func positionCmd(subFlags *flag.FlagSet, args []string) error { subFlags.Parse(args) if len(args) < 3 { return fmt.Errorf("Not enough arguments for position operation.") } pos1, err := replication.DecodePosition(args[1]) if err != nil { return err } switch args[0] { case "equal": pos2, err := replication.DecodePosition(args[2]) if err != nil { return err } fmt.Println(pos1.Equal(pos2)) case "at_least": pos2, err := replication.DecodePosition(args[2]) if err != nil { return err } fmt.Println(pos1.AtLeast(pos2)) case "append": gtid, err := replication.DecodeGTID(args[2]) if err != nil { return err } fmt.Println(replication.AppendGTID(pos1, gtid)) } return nil }
// writeRecoveryPosition will write the current GTID as the recovery position // for the next transaction. // We will also try to get the timestamp for the transaction. Two cases: // - we have statements, and they start with a SET TIMESTAMP that we // can parse: then we update transaction_timestamp in blp_checkpoint // with it, and set SecondsBehindMaster to now() - transaction_timestamp // - otherwise (the statements are probably filtered out), we leave // transaction_timestamp alone (keeping the old value), and we don't // change SecondsBehindMaster func (blp *BinlogPlayer) writeRecoveryPosition(tx *pb.BinlogTransaction) error { gtid, err := replication.DecodeGTID(tx.TransactionId) if err != nil { return err } now := time.Now().Unix() blp.position = replication.AppendGTID(blp.position, gtid) updateRecovery := updateBlpCheckpoint(blp.uid, blp.position, now, tx.Timestamp) qr, err := blp.exec(updateRecovery) if err != nil { return fmt.Errorf("Error %v in writing recovery info %v", err, updateRecovery) } if qr.RowsAffected != 1 { return fmt.Errorf("Cannot update blp_recovery table, affected %v rows", qr.RowsAffected) } blp.blplStats.SetLastPosition(blp.position) if tx.Timestamp != 0 { blp.blplStats.SecondsBehindMaster.Set(now - tx.Timestamp) } return nil }
// AppendGTID updates the current replication position by appending a GTID to // the set of transactions that have been processed. func (rci *RowcacheInvalidator) AppendGTID(gtid replication.GTID) { rci.posMutex.Lock() defer rci.posMutex.Unlock() rci.pos = replication.AppendGTID(rci.pos, gtid) }
// parseEvents processes the raw binlog dump stream from the server, one event // at a time, and groups them into transactions. It is called from within the // service function launched by Stream(). // // If the sendTransaction func returns io.EOF, parseEvents returns ErrClientEOF. // If the events channel is closed, parseEvents returns ErrServerEOF. // If the context is done, returns ctx.Err(). func (bls *Streamer) parseEvents(ctx context.Context, events <-chan replication.BinlogEvent) (replication.Position, error) { var statements []*binlogdatapb.BinlogTransaction_Statement var format replication.BinlogFormat var gtid replication.GTID var pos = bls.startPos var autocommit = true var err error // A begin can be triggered either by a BEGIN query, or by a GTID_EVENT. begin := func() { if statements != nil { // If this happened, it would be a legitimate error. log.Errorf("BEGIN in binlog stream while still in another transaction; dropping %d statements: %v", len(statements), statements) binlogStreamerErrors.Add("ParseEvents", 1) } statements = make([]*binlogdatapb.BinlogTransaction_Statement, 0, 10) autocommit = false } // A commit can be triggered either by a COMMIT query, or by an XID_EVENT. // Statements that aren't wrapped in BEGIN/COMMIT are committed immediately. commit := func(timestamp uint32) error { if int64(timestamp) >= bls.timestamp { trans := &binlogdatapb.BinlogTransaction{ Statements: statements, EventToken: &querypb.EventToken{ Timestamp: int64(timestamp), Position: replication.EncodePosition(pos), }, } if err = bls.sendTransaction(trans); err != nil { if err == io.EOF { return ErrClientEOF } return fmt.Errorf("send reply error: %v", err) } } statements = nil autocommit = true return nil } // Parse events. for { var ev replication.BinlogEvent var ok bool select { case ev, ok = <-events: if !ok { // events channel has been closed, which means the connection died. log.Infof("reached end of binlog event stream") return pos, ErrServerEOF } case <-ctx.Done(): log.Infof("stopping early due to binlog Streamer service shutdown or client disconnect") return pos, ctx.Err() } // Validate the buffer before reading fields from it. if !ev.IsValid() { return pos, fmt.Errorf("can't parse binlog event, invalid data: %#v", ev) } // We need to keep checking for FORMAT_DESCRIPTION_EVENT even after we've // seen one, because another one might come along (e.g. on log rotate due to // binlog settings change) that changes the format. if ev.IsFormatDescription() { format, err = ev.Format() if err != nil { return pos, fmt.Errorf("can't parse FORMAT_DESCRIPTION_EVENT: %v, event data: %#v", err, ev) } continue } // We can't parse anything until we get a FORMAT_DESCRIPTION_EVENT that // tells us the size of the event header. if format.IsZero() { // The only thing that should come before the FORMAT_DESCRIPTION_EVENT // is a fake ROTATE_EVENT, which the master sends to tell us the name // of the current log file. if ev.IsRotate() { continue } return pos, fmt.Errorf("got a real event before FORMAT_DESCRIPTION_EVENT: %#v", ev) } // Strip the checksum, if any. We don't actually verify the checksum, so discard it. ev, _, err = ev.StripChecksum(format) if err != nil { return pos, fmt.Errorf("can't strip checksum from binlog event: %v, event data: %#v", err, ev) } // Update the GTID if the event has one. The actual event type could be // something special like GTID_EVENT (MariaDB, MySQL 5.6), or it could be // an arbitrary event with a GTID in the header (Google MySQL). if ev.HasGTID(format) { gtid, err = ev.GTID(format) if err != nil { return pos, fmt.Errorf("can't get GTID from binlog event: %v, event data: %#v", err, ev) } pos = replication.AppendGTID(pos, gtid) } switch { case ev.IsGTID(): // GTID_EVENT if ev.IsBeginGTID(format) { begin() } case ev.IsXID(): // XID_EVENT (equivalent to COMMIT) if err = commit(ev.Timestamp()); err != nil { return pos, err } case ev.IsIntVar(): // INTVAR_EVENT name, value, err := ev.IntVar(format) if err != nil { return pos, fmt.Errorf("can't parse INTVAR_EVENT: %v, event data: %#v", err, ev) } statements = append(statements, &binlogdatapb.BinlogTransaction_Statement{ Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte(fmt.Sprintf("SET %s=%d", name, value)), }) case ev.IsRand(): // RAND_EVENT seed1, seed2, err := ev.Rand(format) if err != nil { return pos, fmt.Errorf("can't parse RAND_EVENT: %v, event data: %#v", err, ev) } statements = append(statements, &binlogdatapb.BinlogTransaction_Statement{ Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte(fmt.Sprintf("SET @@RAND_SEED1=%d, @@RAND_SEED2=%d", seed1, seed2)), }) case ev.IsQuery(): // QUERY_EVENT // Extract the query string and group into transactions. q, err := ev.Query(format) if err != nil { return pos, fmt.Errorf("can't get query from binlog event: %v, event data: %#v", err, ev) } switch cat := getStatementCategory(q.SQL); cat { case binlogdatapb.BinlogTransaction_Statement_BL_BEGIN: begin() case binlogdatapb.BinlogTransaction_Statement_BL_ROLLBACK: // Rollbacks are possible under some circumstances. Since the stream // client keeps track of its replication position by updating the set // of GTIDs it's seen, we must commit an empty transaction so the client // can update its position. statements = nil fallthrough case binlogdatapb.BinlogTransaction_Statement_BL_COMMIT: if err = commit(ev.Timestamp()); err != nil { return pos, err } default: // BL_DDL, BL_DML, BL_SET, BL_UNRECOGNIZED if q.Database != "" && q.Database != bls.dbname { // Skip cross-db statements. continue } setTimestamp := &binlogdatapb.BinlogTransaction_Statement{ Category: binlogdatapb.BinlogTransaction_Statement_BL_SET, Sql: []byte(fmt.Sprintf("SET TIMESTAMP=%d", ev.Timestamp())), } statement := &binlogdatapb.BinlogTransaction_Statement{ Category: cat, Sql: []byte(q.SQL), } // If the statement has a charset and it's different than our client's // default charset, send it along with the statement. // If our client hasn't told us its charset, always send it. if bls.clientCharset == nil || (q.Charset != nil && *q.Charset != *bls.clientCharset) { setTimestamp.Charset = q.Charset statement.Charset = q.Charset } statements = append(statements, setTimestamp, statement) if autocommit { if err = commit(ev.Timestamp()); err != nil { return pos, err } } } case ev.IsPreviousGTIDs(): // PREVIOUS_GTIDS_EVENT // MySQL 5.6 only: The Binlogs contain an // event that gives us all the previously // applied commits. It is *not* an // authoritative value, unless we started from // the beginning of a binlog file. if !bls.usePreviousGTIDs { continue } newPos, err := ev.PreviousGTIDs(format) if err != nil { return pos, err } pos = newPos if err = commit(ev.Timestamp()); err != nil { return pos, err } } } }