Skip to content
This repository has been archived by the owner on Nov 24, 2023. It is now read-only.

relay: use Reader interface read binlog events #92

Merged
merged 9 commits into from
Mar 28, 2019
66 changes: 66 additions & 0 deletions pkg/binlog/reader/reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2019 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package reader

import (
"context"

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

"github.com/pingcap/dm/pkg/gtid"
)

type readerStage int

const (
stageNew readerStage = iota
stagePrepared
stageClosed
)

// String implements Stringer.String.
func (s readerStage) String() string {
switch s {
case stageNew:
return "new"
case stagePrepared:
return "prepared"
case stageClosed:
return "closed"
default:
return "unknown"
}
}

// Reader is a binlog event reader, it may read binlog events from a TCP stream, binlog files or any other in-memory buffer.
// One reader should read binlog events either through position mode or GTID mode.
type Reader interface {
// StartSyncByPos prepares the reader for reading binlog from the specified position.
StartSyncByPos(pos gmysql.Position) error

// StartSyncByGTID prepares the reader for reading binlog from the specified GTID set.
StartSyncByGTID(gSet gtid.Set) error

// Close closes the reader and release the resource.
Close() error

// GetEvent gets the binlog event one by one, it will block if no event can be read.
// You can pass a context (like Cancel or Timeout) to break the block.
// If you do not want to check the stage (for reducing the lock operation), you can set `checkStage` to false.
GetEvent(ctx context.Context, checkStage bool) (*replication.BinlogEvent, error)

// Status returns the status of the reader.
Status() interface{}
}
163 changes: 163 additions & 0 deletions pkg/binlog/reader/tcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2019 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package reader

import (
"context"
"database/sql"
"encoding/json"
"fmt"
"sync"

"github.com/pingcap/errors"
gmysql "github.com/siddontang/go-mysql/mysql"
"github.com/siddontang/go-mysql/replication"

"github.com/pingcap/dm/pkg/gtid"
"github.com/pingcap/dm/pkg/log"
"github.com/pingcap/dm/pkg/utils"
)

// TCPReader is a binlog event reader which read binlog events from a TCP stream.
type TCPReader struct {
stageMu sync.Mutex
stage readerStage

syncerCfg replication.BinlogSyncerConfig
syncer *replication.BinlogSyncer
streamer *replication.BinlogStreamer
}

// TCPReaderStatus represents the status of a TCPReader.
type TCPReaderStatus struct {
Stage string `json:"stage"`
Connection uint32 `json:"connection"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

what's meaning of Connection?

Copy link
Member Author

Choose a reason for hiding this comment

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

renamed to ConnID, but keep the json flag as connection.

}

// String implements Stringer.String.
func (s *TCPReaderStatus) String() string {
data, err := json.Marshal(s)
if err != nil {
log.Errorf("[TCPReaderStatus] marshal status to json error %v", err)
}
return string(data)
}

// NewTCPReader creates a TCPReader instance.
func NewTCPReader(syncerCfg replication.BinlogSyncerConfig) Reader {
return &TCPReader{
syncerCfg: syncerCfg,
syncer: replication.NewBinlogSyncer(syncerCfg),
}
}

// StartSyncByPos implements Reader.StartSyncByPos.
func (r *TCPReader) StartSyncByPos(pos gmysql.Position) error {
r.stageMu.Lock()
defer r.stageMu.Unlock()

if r.stage != stageNew {
return errors.NotValidf("stage %d, expect %d", r.stage, stageNew)
Copy link
Collaborator

Choose a reason for hiding this comment

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

is log stage %d, expect %d is not valid?

Copy link
Member Author

Choose a reason for hiding this comment

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

changed to stage %s, expect %s only.

}

streamer, err := r.syncer.StartSync(pos)
if err != nil {
return errors.Annotatef(err, "start sync from position %s", pos)
}

r.streamer = streamer
r.stage = stagePrepared
return nil
}

// StartSyncByGTID implements Reader.StartSyncByGTID.
func (r *TCPReader) StartSyncByGTID(gSet gtid.Set) error {
r.stageMu.Lock()
Copy link
Collaborator

Choose a reason for hiding this comment

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

in fact this lock prevent more than stage field

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, rename to mu.

defer r.stageMu.Unlock()

if r.stage != stageNew {
return errors.NotValidf("stage %d, expect %d", r.stage, stageNew)
}

if gSet == nil {
return errors.NotValidf("nil GTID set")
}

streamer, err := r.syncer.StartSyncGTID(gSet.Origin())
if err != nil {
return errors.Annotatef(err, "start sync from GTID set %s", gSet)
}

r.streamer = streamer
r.stage = stagePrepared
return nil
}

// Close implements Reader.Close.
func (r *TCPReader) Close() error {
r.stageMu.Lock()
defer r.stageMu.Unlock()

if r.stage == stageClosed {
return errors.NotValidf("already closed")
}

connID := r.syncer.LastConnectionID()
if connID > 0 {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8mb4",
r.syncerCfg.User, r.syncerCfg.Password, r.syncerCfg.Host, r.syncerCfg.Port)
db, err := sql.Open("mysql", dsn)
if err != nil {
return errors.Annotate(err, "open connection to the master")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can output which master

Copy link
Member Author

Choose a reason for hiding this comment

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

added

}
defer db.Close()
err = utils.KillConn(db, connID)
if err != nil {
return errors.Annotatef(err, "kill connection %d", connID)
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Copy link
Member Author

Choose a reason for hiding this comment

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

added

}
}

r.stage = stageClosed
return nil
}

// GetEvent implements Reader.GetEvent.
func (r *TCPReader) GetEvent(ctx context.Context, checkStage bool) (*replication.BinlogEvent, error) {
if checkStage {
r.stageMu.Lock()
if r.stage != stagePrepared {
Copy link
Collaborator

Choose a reason for hiding this comment

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

would atomic operation be better?

Copy link
Member Author

Choose a reason for hiding this comment

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

changed to use atomic operation, and removed checkStage.

r.stageMu.Unlock()
return nil, errors.NotValidf("stage %d, expect %d", r.stage, stagePrepared)
}
r.stageMu.Unlock()
}

return r.streamer.GetEvent(ctx)
}

// Status implements Reader.Status.
func (r *TCPReader) Status() interface{} {
r.stageMu.Lock()
stage := r.stage
r.stageMu.Unlock()

var connID uint32
if stage != stageNew {
connID = r.syncer.LastConnectionID()
}
return &TCPReaderStatus{
Stage: stage.String(),
Connection: connID,
}
}
Loading