binlogignoredb mysql_replication/binlogsyncer.go · siddontang/go-mysql - Gitee.com

package replication

import (

"encoding/binary"

"errors"

"fmt"

"sync"

"time"

"github.com/satori/go.uuid"

"github.com/siddontang/go-mysql/client"

. "github.com/siddontang/go-mysql/mysql"

)

var (

errSyncRunning = errors.New("Sync is running, must Close first")

errNotRegistered = errors.New("Syncer is not registered as a slave")

)

type BinlogSyncer struct {

m sync.Mutex

flavor string

c *client.Conn

serverID uint32

host string

port uint16

user string

password string

masterID uint32

wg sync.WaitGroup

parser *BinlogParser

nextPos Position

running bool

}

func NewBinlogSyncer(serverID uint32, flavor string) *BinlogSyncer {

b := new(BinlogSyncer)

b.flavor = flavor

b.serverID = serverID

b.masterID = 0

b.parser = NewBinlogParser()

b.running = false

return b

}

func (b *BinlogSyncer) Close() {

b.m.Lock()

defer b.m.Unlock()

b.close()

}

func (b *BinlogSyncer) close() {

if b.c != nil {

b.c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))

}

b.wg.Wait()

if b.c != nil {

b.c.Close()

}

b.running = false

b.c = nil

}

func (b *BinlogSyncer) checkExec() error {

if b.running {

return errSyncRunning

} else if b.c == nil {

return errNotRegistered

}

return nil

}

func (b *BinlogSyncer) GetMasterUUID() (uuid.UUID, error) {

b.m.Lock()

defer b.m.Unlock()

if err := b.checkExec(); err != nil {

return uuid.UUID{}, err

}

if r, err := b.c.Execute("SHOW GLOBAL VARIABLES LIKE 'SERVER_UUID'"); err != nil {

return uuid.UUID{}, err

} else {

s, _ := r.GetString(0, 1)

if s == "" || s == "NONE" {

return uuid.UUID{}, nil

} else {

return uuid.FromString(s)

}

}

}

// You must register slave at first before you do other operations

// This function will close old replication sync if exists

func (b *BinlogSyncer) RegisterSlave(host string, port uint16, user string, password string) error {

b.m.Lock()

defer b.m.Unlock()

// first, close old replication sync

b.close()

b.host = host

b.port = port

b.user = user

b.password = password

err := b.registerSlave()

if err != nil {

b.close()

}

return err

}

// If you close sync before and want to restart again, you can call this before other operations

// This function will close old replication sync if exists

func (b *BinlogSyncer) ReRegisterSlave() error {

b.m.Lock()

defer b.m.Unlock()

if len(b.host) == 0 || len(b.user) == 0 {

return fmt.Errorf("empty host and user, you must register slave before")

}

b.close()

err := b.registerSlave()

if err != nil {

b.close()

}

return err

}

func (b *BinlogSyncer) registerSlave() error {

var err error

b.c, err = client.Connect(fmt.Sprintf("%s:%d", b.host, b.port), b.user, b.password, "")

if err != nil {

return err

}

//for mysql 5.6+, binlog has a crc32 checksum

//before mysql 5.6, this will not work, don't matter.:-)

if r, err := b.c.Execute("SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'"); err != nil {

return err

} else {

s, _ := r.GetString(0, 1)

if s != "" {

// maybe CRC32 or NONE

// mysqlbinlog.cc use NONE, see its below comments:

// Make a notice to the server that this client

// is checksum-aware. It does not need the first fake Rotate

// necessary checksummed.

// That preference is specified below.

if _, err = b.c.Execute(`SET @master_binlog_checksum='NONE'`); err != nil {

return err

}

// if _, err = b.c.Execute(`SET @master_binlog_checksum=@@global.binlog_checksum`); err != nil {

// return err

// }

}

}

if err = b.writeRegisterSlaveCommand(); err != nil {

return err

}

if _, err = b.c.ReadOKPacket(); err != nil {

return err

}

return nil

}

func (b *BinlogSyncer) EnableSemiSync() error {

b.m.Lock()

defer b.m.Unlock()

if err := b.checkExec(); err != nil {

return err

}

if r, err := b.c.Execute("SHOW VARIABLES LIKE 'rpl_semi_sync_master_enabled';"); err != nil {

return err

} else {

s, _ := r.GetString(0, 1)

if s != "ON" {

return fmt.Errorf("master does not support semi synchronous replication")

}

}

_, err := b.c.Execute(`SET @rpl_semi_sync_slave = 1;`)

return err

}

func (b *BinlogSyncer) startDumpStream() *BinlogStreamer {

b.running = true

s := newBinlogStreamer()

b.wg.Add(1)

go b.onStream(s)

return s

}

func (b *BinlogSyncer) StartSync(pos Position) (*BinlogStreamer, error) {

b.m.Lock()

defer b.m.Unlock()

if err := b.checkExec(); err != nil {

return nil, err

}

//always start from position 4

if pos.Pos < 4 {

pos.Pos = 4

}

err := b.writeBinglogDumpCommand(pos)

if err != nil {

return nil, err

}

return b.startDumpStream(), nil

}

func (b *BinlogSyncer) SetRawMode(mode bool) error {

b.m.Lock()

defer b.m.Unlock()

if err := b.checkExec(); err != nil {

return err

}

b.parser.SetRawMode(mode)

return nil

}

func (b *BinlogSyncer) ExecuteSql(query string, args ...interface{}) (*Result, error) {

b.m.Lock()

defer b.m.Unlock()

if err := b.checkExec(); err != nil {

return nil, err

}

return b.c.Execute(query, args...)

}

func (b *BinlogSyncer) StartSyncGTID(gset GTIDSet) (*BinlogStreamer, error) {

b.m.Lock()

defer b.m.Unlock()

if err := b.checkExec(); err != nil {

return nil, err

}

var err error

switch b.flavor {

case MySQLFlavor:

err = b.writeBinlogDumpMysqlGTIDCommand(gset)

case MariaDBFlavor:

err = b.writeBinlogDumpMariadbGTIDCommand(gset)

default:

err = fmt.Errorf("invalid flavor %s", b.flavor)

}

if err != nil {

return nil, err

}

return b.startDumpStream(), nil

}

func (b *BinlogSyncer) writeBinglogDumpCommand(p Position) error {

b.c.ResetSequence()

data := make([]byte, 4+1+4+2+4+len(p.Name))

pos := 4

data[pos] = COM_BINLOG_DUMP

pos++

binary.LittleEndian.PutUint32(data[pos:], p.Pos)

pos += 4

binary.LittleEndian.PutUint16(data[pos:], BINLOG_DUMP_NEVER_STOP)

pos += 2

binary.LittleEndian.PutUint32(data[pos:], b.serverID)

pos += 4

copy(data[pos:], p.Name)

return b.c.WritePacket(data)

}

func (b *BinlogSyncer) writeBinlogDumpMysqlGTIDCommand(gset GTIDSet) error {

p := Position{"", 4}

gtidData := gset.Encode()

b.c.ResetSequence()

data := make([]byte, 4+1+2+4+4+len(p.Name)+8+4+len(gtidData))

pos := 4

data[pos] = COM_BINLOG_DUMP_GTID

pos++

binary.LittleEndian.PutUint16(data[pos:], 0)

pos += 2

binary.LittleEndian.PutUint32(data[pos:], b.serverID)

pos += 4

binary.LittleEndian.PutUint32(data[pos:], uint32(len(p.Name)))

pos += 4

n := copy(data[pos:], p.Name)

pos += n

binary.LittleEndian.PutUint64(data[pos:], uint64(p.Pos))

pos += 8

binary.LittleEndian.PutUint32(data[pos:], uint32(len(gtidData)))

pos += 4

n = copy(data[pos:], gtidData)

pos += n

data = data[0:pos]

return b.c.WritePacket(data)

}

func (b *BinlogSyncer) writeBinlogDumpMariadbGTIDCommand(gset GTIDSet) error {

// Copy from vitess

startPos := gset.String()

// Tell the server that we understand GTIDs by setting our slave capability

// to MARIA_SLAVE_CAPABILITY_GTID = 4 (MariaDB >= 10.0.1).

if _, err := b.c.Execute("SET @mariadb_slave_capability=4"); err != nil {

return fmt.Errorf("failed to set @mariadb_slave_capability=4: %v", err)

}

// Set the slave_connect_state variable before issuing COM_BINLOG_DUMP to

// provide the start position in GTID form.

query := fmt.Sprintf("SET @slave_connect_state='%s'", startPos)

if _, err := b.c.Execute(query); err != nil {

return fmt.Errorf("failed to set @slave_connect_state='%s': %v", startPos, err)

}

// Real slaves set this upon connecting if their gtid_strict_mode option was

// enabled. We always use gtid_strict_mode because we need it to make our

// internal GTID comparisons safe.

if _, err := b.c.Execute("SET @slave_gtid_strict_mode=1"); err != nil {

return fmt.Errorf("failed to set @slave_gtid_strict_mode=1: %v", err)

}

// Since we use @slave_connect_state, the file and position here are ignored.

return b.writeBinglogDumpCommand(Position{"", 0})

}

func (b *BinlogSyncer) writeRegisterSlaveCommand() error {

b.c.ResetSequence()

data := make([]byte, 4+1+4+1+len(b.host)+1+len(b.user)+1+len(b.password)+2+4+4)

pos := 4

data[pos] = COM_REGISTER_SLAVE

pos++

binary.LittleEndian.PutUint32(data[pos:], b.serverID)

pos += 4

data[pos] = uint8(len(b.host))

pos++

n := copy(data[pos:], b.host)

pos += n

data[pos] = uint8(len(b.user))

pos++

n = copy(data[pos:], b.user)

pos += n

data[pos] = uint8(len(b.password))

pos++

n = copy(data[pos:], b.password)

pos += n

binary.LittleEndian.PutUint16(data[pos:], b.port)

pos += 2

//replication rank, not used

binary.LittleEndian.PutUint32(data[pos:], 0)

pos += 4

binary.LittleEndian.PutUint32(data[pos:], b.masterID)

return b.c.WritePacket(data)

}

func (b *BinlogSyncer) replySemiSyncACK(p Position) error {

b.c.ResetSequence()

data := make([]byte, 4+1+8+len(p.Name))

pos := 4

// semi sync indicator

data[pos] = SemiSyncIndicator

pos++

binary.LittleEndian.PutUint64(data[pos:], uint64(p.Pos))

pos += 8

copy(data[pos:], p.Name)

err := b.c.WritePacket(data)

if err != nil {

return err

}

_, err = b.c.ReadOKPacket()

if err != nil {

}

return err

}

func (b *BinlogSyncer) onStream(s *BinlogStreamer) {

defer func() {

if e := recover(); e != nil {

s.closeWithError(fmt.Errorf("Err: %v\n Stack: %s", e, Pstack()))

}

b.wg.Done()

}()

for {

data, err := b.c.ReadPacket()

if err != nil {

s.closeWithError(err)

return

}

switch data[0] {

case OK_HEADER:

if err = b.parseEvent(s, data); err != nil {

s.closeWithError(err)

return

}

case ERR_HEADER:

err = b.c.HandleErrorPacket(data)

s.closeWithError(err)

return

default:

s.closeWithError(fmt.Errorf("invalid stream header %c", data[0]))

return

}

}

}

func (b *BinlogSyncer) parseEvent(s *BinlogStreamer, data []byte) error {

//skip 0x00

data = data[1:]

needACK := false

if data[0] == SemiSyncIndicator {

needACK = (data[1] == 0x01)

//skip semi sync header

data = data[2:]

}

e, err := b.parser.parse(data)

if err != nil {

return err

}

b.nextPos.Pos = e.Header.LogPos

if re, ok := e.Event.(*RotateEvent); ok {

b.nextPos.Name = string(re.NextLogName)

b.nextPos.Pos = uint32(re.Position)

}

s.ch

if needACK {

err := b.replySemiSyncACK(b.nextPos)

if err != nil {

return err

}

}

return nil

}

一键复制

编辑

Web IDE

原始数据

按行查看

历史

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值