Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support download flushed binlog and parse event for cloud comput… #741

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions canal/canal.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type Canal struct {

ctx context.Context
cancel context.CancelFunc

binFileDownload BinlogFileDownload
}

// canal will retry fetching unknown table's meta after UnknownTableRetryPeriod
Expand Down
108 changes: 108 additions & 0 deletions canal/local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package canal

import (
"context"

"github.com/go-mysql-org/go-mysql/mysql"
"github.com/go-mysql-org/go-mysql/replication"
"github.com/pingcap/errors"
)

// BinlogFileDownload download the binlog file from cloud computing platform (etc. aliyun)
type BinlogFileDownload func(mysql.Position) (localBinFilePath string, err error)
lance6716 marked this conversation as resolved.
Show resolved Hide resolved

// WithLocalBinlogDownload registers the local bin file download,
// that allows download the flushed binlog file to local (etc. aliyun)
func (c *Canal) WithLocalBinlogDownload(d BinlogFileDownload) {
c.binFileDownload = d
}

func (c *Canal) adaptLocalBinFileStreamer(syncMasterStreamer *replication.BinlogStreamer, err error) (*LocalBinFileAdapterStreamer, error) {
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
return &LocalBinFileAdapterStreamer{
BinlogStreamer: syncMasterStreamer,
syncMasterStreamer: syncMasterStreamer,
canal: c,
binFileDownload: c.binFileDownload,
}, err
}

// LocalBinFileAdapterStreamer will support to download flushed binlog file for continuous sync in cloud computing platform
type LocalBinFileAdapterStreamer struct {
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
*replication.BinlogStreamer // the running streamer, it will be localStreamer or sync master streamer
syncMasterStreamer *replication.BinlogStreamer // syncMasterStreamer is the streamer from startSyncer
canal *Canal
binFileDownload BinlogFileDownload
}

// GetEvent will auto switch the running streamer and return replication.BinlogEvent
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
func (s *LocalBinFileAdapterStreamer) GetEvent(ctx context.Context) (*replication.BinlogEvent, error) {
if s.binFileDownload == nil { // not support to use local bin file
return s.BinlogStreamer.GetEvent(ctx)
}

ev, err := s.BinlogStreamer.GetEvent(ctx)

if err == nil {
switch ev.Event.(type) {
case *replication.RotateEvent: // RotateEvent means need to change steamer back to sync master to retry sync
s.BinlogStreamer = s.syncMasterStreamer
}
return ev, err
}

if err == replication.ErrNeedSyncAgain { // restart master if last sync master syncer has error
s.canal.syncer.Close()
_ = s.canal.prepareSyncer()

newStreamer, startErr := s.canal.startSyncer()
if startErr == nil {
ev, err = newStreamer.GetEvent(ctx)
}
// set all streamer to the new sync master streamer
s.BinlogStreamer = newStreamer
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
s.syncMasterStreamer = newStreamer
}

if mysqlErr, ok := err.(*mysql.MyError); ok {
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
// change to local binlog file streamer to adapter the steamer
if mysqlErr.Code == mysql.ER_MASTER_FATAL_ERROR_READING_BINLOG &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the binlog file is purged just at the time we are switching to it, should we also generate a "fake rotate evnet" as the real binlog streamer?

https://github.com/mysql/mysql-server/blob/4f1d7cf5fcb11a3f84cff27e37100d7295e7d5ca/sql/rpl_binlog_sender.cc#L248

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if syncer switch to a purged binlog file, 'not find first log file' happen and syncer closed. it try to download binlog to local. the local file contains rotate event, and will put the rotate event to streamer. then canal's masterInfo will change to the new position and it restart syncer to try if new position file is on master, if not the new started syncer will also closed by 'not find first log file' and download next binlog to local. so generate a "fake rotate evnet" maybe not necessary?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the "fake rorate event" which comes before the beginning of any events in binlog, not the real rotate event at the end of each binlog file. You can find it when reading binlog

=== RotateEvent ===
Date: 2022-12-08 11:13:59
Log position: 1969
Event size: 47
Position: 4
Next log name: mysql-bin.000002

=== RotateEvent ===
Date: 1970-01-01 08:00:00
Log position: 0
Event size: 47
Position: 4
Next log name: mysql-bin.000002

for above two events, the first one is the real rotate event which can be found at the end of mysql-bin.000001, and the second one is a fake rotate event

Copy link
Contributor Author

@BLAZZ BLAZZ Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"fake rorate event" not exist in binlog file, so no need to process?
20221208183603
I find this in mariadb docs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"fake rorate event" does not exist in binlog file but it is sent to downstream and can be find when reading binlog 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when using local binlog, the real syncer is closed, canal will not read event from server's binlog, all event read from AdapterStreamer is parsed from binlog file, that will not exist "fake rorate event". "fake rorate event" will send to streamer after finish parse binlog file and restart the read syncer, and runSyncBinlog will process that event

mysqlErr.Message == "Could not find first log file name in binary log index file" {
gset := s.canal.master.GTIDSet()
if gset == nil || gset.String() == "" { // currently only support xid mode
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
s.canal.cfg.Logger.Info("Could not find first log, try to download the local binlog for retry")
pos := s.canal.master.Position()
newStreamer := newLocalBinFileStreamer(s.binFileDownload, pos)

s.syncMasterStreamer = s.BinlogStreamer
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
s.BinlogStreamer = newStreamer

return newStreamer.GetEvent(ctx)
}
}
}

return ev, err
}

func newLocalBinFileStreamer(download BinlogFileDownload, position mysql.Position) *replication.BinlogStreamer {
streamer := replication.NewBinlogStreamer()
binFilePath, err := download(position)
if err != nil {
streamer.CloseWithError(errors.New("local binlog file not exist"))
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
}

go func(binFilePath string, streamer *replication.BinlogStreamer) {
beginFromHere := false
_ = replication.NewBinlogParser().ParseFile(binFilePath, 0, func(be *replication.BinlogEvent) error {
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
if be.Header.LogPos == position.Pos || position.Pos == 4 { // go ahead to check if begin
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
beginFromHere = true
}
if beginFromHere {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to add an error that no matching position for position.Pos? For example user downlaods wrong binlog file

streamer.PutEvent(be)
lance6716 marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
})
}(binFilePath, streamer)

return streamer
}
2 changes: 1 addition & 1 deletion canal/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (c *Canal) startSyncer() (*replication.BinlogStreamer, error) {
}

func (c *Canal) runSyncBinlog() error {
s, err := c.startSyncer()
s, err := c.adaptLocalBinFileStreamer(c.startSyncer())
if err != nil {
return err
}
Expand Down
13 changes: 13 additions & 0 deletions replication/binlogstreamer.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,16 @@ func newBinlogStreamer() *BinlogStreamer {

return s
}

// PutEvent puts event to BinlogStreamer
func (s *BinlogStreamer) PutEvent(ev *BinlogEvent) {
s.ch <- ev
}

func (s *BinlogStreamer) CloseWithError(err error) {
s.closeWithError(err)
}

func NewBinlogStreamer() *BinlogStreamer {
return newBinlogStreamer()
}