You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
8.0 KiB
344 lines
8.0 KiB
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/go-mysql-org/go-mysql/mysql"
|
|
"github.com/go-mysql-org/go-mysql/replication"
|
|
)
|
|
|
|
// SQLWriter 用于异步输出SQL语句
|
|
type SQLWriter struct {
|
|
sqlCh chan string
|
|
}
|
|
|
|
func NewSQLWriter() *SQLWriter {
|
|
return &SQLWriter{
|
|
sqlCh: make(chan string, 1000), // 缓冲通道
|
|
}
|
|
}
|
|
|
|
// Start 启动异步SQL写入器
|
|
func (w *SQLWriter) Start() {
|
|
go func() {
|
|
for sql := range w.sqlCh {
|
|
// 这里可以替换为实际写入文件、发送到消息队列等操作
|
|
fmt.Printf("[%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), sql)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Write 异步写入SQL
|
|
func (w *SQLWriter) Write(sql string) {
|
|
select {
|
|
case w.sqlCh <- sql:
|
|
// 成功写入通道
|
|
default:
|
|
log.Println("警告: SQL通道拥堵,丢弃SQL语句:", sql)
|
|
}
|
|
}
|
|
|
|
// Stop 停止写入器
|
|
func (w *SQLWriter) Stop() {
|
|
close(w.sqlCh)
|
|
}
|
|
|
|
type PositionManager struct {
|
|
filePath string
|
|
}
|
|
|
|
func (pm *PositionManager) Save(pos mysql.Position) error {
|
|
data, _ := json.Marshal(pos)
|
|
return os.WriteFile(pm.filePath, data, 0644)
|
|
}
|
|
|
|
func (pm *PositionManager) Load() (mysql.Position, error) {
|
|
data, err := os.ReadFile(pm.filePath)
|
|
if err != nil {
|
|
return mysql.Position{}, err
|
|
}
|
|
|
|
var pos mysql.Position
|
|
err = json.Unmarshal(data, &pos)
|
|
return pos, err
|
|
}
|
|
|
|
// 程序启动参数
|
|
var user = flag.String("user", "root", "MySQL user, must have replication privilege")
|
|
var password = flag.String("password", "****", "MySQL password")
|
|
|
|
// 从 arg参数中获取配置信息
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
// 创建SQL写入器
|
|
sqlWriter := NewSQLWriter()
|
|
sqlWriter.Start()
|
|
defer sqlWriter.Stop()
|
|
|
|
// 配置Binlog同步器
|
|
cfg := replication.BinlogSyncerConfig{
|
|
ServerID: 100, // 唯一的ServerID
|
|
Flavor: "mysql",
|
|
Host: "localhost",
|
|
Port: 3306,
|
|
User: *user,
|
|
Password: *password,
|
|
Charset: "utf8mb4",
|
|
}
|
|
|
|
syncer := replication.NewBinlogSyncer(cfg)
|
|
defer syncer.Close()
|
|
|
|
// 获取当前Binlog位置(可选)
|
|
// 也可以从指定的位置开始,如 mysql.Position{Name: "mysql-bin.000001", Pos: 4}
|
|
position := mysql.Position{Name: "", Pos: 4}
|
|
|
|
streamer, err := syncer.StartSync(position)
|
|
if err != nil {
|
|
log.Fatalf("Failed to start sync: %v", err)
|
|
}
|
|
|
|
log.Println("开始监听MySQL Binlog...")
|
|
|
|
// 处理优雅退出
|
|
signalCh := make(chan os.Signal, 1)
|
|
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// 启动事件处理循环
|
|
go eventLoop(ctx, streamer, sqlWriter)
|
|
|
|
<-signalCh
|
|
log.Println("收到退出信号,停止监听...")
|
|
}
|
|
|
|
// 事件处理循环
|
|
func eventLoop(ctx context.Context, streamer *replication.BinlogStreamer, writer *SQLWriter) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
ev, err := streamer.GetEvent(ctx)
|
|
if err != nil {
|
|
if err == context.Canceled {
|
|
return
|
|
}
|
|
log.Printf("获取Binlog事件错误: %v", err)
|
|
continue
|
|
}
|
|
|
|
// 解析Binlog事件
|
|
if err := parseBinlogEvent(ev, writer); err != nil {
|
|
log.Printf("解析Binlog事件错误: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 解析Binlog事件并生成SQL语句
|
|
func parseBinlogEvent(ev *replication.BinlogEvent, writer *SQLWriter) error {
|
|
event := ev.Header.EventType
|
|
|
|
switch event {
|
|
case replication.WRITE_ROWS_EVENTv1, replication.WRITE_ROWS_EVENTv2:
|
|
return handleWriteRows(ev, writer)
|
|
case replication.DELETE_ROWS_EVENTv1, replication.DELETE_ROWS_EVENTv2:
|
|
return handleDeleteRows(ev, writer)
|
|
case replication.UPDATE_ROWS_EVENTv1, replication.UPDATE_ROWS_EVENTv2:
|
|
return handleUpdateRows(ev, writer)
|
|
case replication.QUERY_EVENT:
|
|
return handleQueryEvent(ev, writer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 处理INSERT事件
|
|
func handleWriteRows(ev *replication.BinlogEvent, writer *SQLWriter) error {
|
|
rowsEvent, ok := ev.Event.(*replication.RowsEvent)
|
|
if !ok {
|
|
return fmt.Errorf("类型断言失败: 期望*replication.RowsEvent")
|
|
}
|
|
|
|
tableName := string(rowsEvent.Table.Table)
|
|
schemaName := string(rowsEvent.Table.Schema)
|
|
|
|
for _, row := range rowsEvent.Rows {
|
|
columns := make([]string, len(row))
|
|
values := make([]interface{}, len(row))
|
|
|
|
for i, value := range row {
|
|
columns[i] = fmt.Sprintf("column%d", i)
|
|
values[i] = value
|
|
}
|
|
|
|
sql := generateInsertSQL(schemaName, tableName, columns, values)
|
|
writer.Write(sql)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 处理DELETE事件
|
|
func handleDeleteRows(ev *replication.BinlogEvent, writer *SQLWriter) error {
|
|
rowsEvent, ok := ev.Event.(*replication.RowsEvent)
|
|
if !ok {
|
|
return fmt.Errorf("类型断言失败: 期望*replication.RowsEvent")
|
|
}
|
|
|
|
tableName := string(rowsEvent.Table.Table)
|
|
schemaName := string(rowsEvent.Table.Schema)
|
|
|
|
for _, row := range rowsEvent.Rows {
|
|
whereClause := generateWhereClause(row)
|
|
sql := fmt.Sprintf("DELETE FROM `%s`.`%s` WHERE %s", schemaName, tableName, whereClause)
|
|
writer.Write(sql)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 处理UPDATE事件
|
|
func handleUpdateRows(ev *replication.BinlogEvent, writer *SQLWriter) error {
|
|
rowsEvent, ok := ev.Event.(*replication.RowsEvent)
|
|
if !ok {
|
|
return fmt.Errorf("类型断言失败: 期望*replication.RowsEvent")
|
|
}
|
|
|
|
tableName := string(rowsEvent.Table.Table)
|
|
schemaName := string(rowsEvent.Table.Schema)
|
|
|
|
// Rows是成对出现的: [旧值, 新值]
|
|
for i := 0; i < len(rowsEvent.Rows); i += 2 {
|
|
if i+1 >= len(rowsEvent.Rows) {
|
|
break
|
|
}
|
|
|
|
oldRow := rowsEvent.Rows[i]
|
|
newRow := rowsEvent.Rows[i+1]
|
|
|
|
setClause := generateSetClause(oldRow, newRow)
|
|
whereClause := generateWhereClause(oldRow)
|
|
|
|
sql := fmt.Sprintf("UPDATE `%s`.`%s` SET %s WHERE %s",
|
|
schemaName, tableName, setClause, whereClause)
|
|
writer.Write(sql)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 处理QUERY事件(DDL语句)
|
|
func handleQueryEvent(ev *replication.BinlogEvent, writer *SQLWriter) error {
|
|
queryEvent, ok := ev.Event.(*replication.QueryEvent)
|
|
if !ok {
|
|
return fmt.Errorf("类型断言失败: 期望*replication.QueryEvent")
|
|
}
|
|
|
|
sql := string(queryEvent.Query)
|
|
writer.Write("-- DDL操作: " + sql)
|
|
|
|
return nil
|
|
}
|
|
|
|
// 生成INSERT SQL语句
|
|
func generateInsertSQL(schema, table string, columns []string, values []interface{}) string {
|
|
valueStrs := make([]string, len(values))
|
|
for i, v := range values {
|
|
valueStrs[i] = formatValue(v)
|
|
}
|
|
|
|
return fmt.Sprintf("INSERT INTO `%s`.`%s` VALUES (%s);",
|
|
schema, table, joinValues(valueStrs))
|
|
}
|
|
|
|
// 生成WHERE子句
|
|
func generateWhereClause(row []interface{}) string {
|
|
parts := make([]string, len(row))
|
|
for i, value := range row {
|
|
parts[i] = fmt.Sprintf("column%d = %s", i, formatValue(value))
|
|
}
|
|
return joinValues(parts)
|
|
}
|
|
|
|
// 生成SET子句
|
|
func generateSetClause(oldRow, newRow []interface{}) string {
|
|
parts := make([]string, len(newRow))
|
|
for i, newValue := range newRow {
|
|
oldValue := oldRow[i]
|
|
// 只更新有变化的字段
|
|
if fmt.Sprintf("%v", oldValue) != fmt.Sprintf("%v", newValue) {
|
|
parts[i] = fmt.Sprintf("column%d = %s", i, formatValue(newValue))
|
|
}
|
|
}
|
|
|
|
// 过滤空值
|
|
var nonEmptyParts []string
|
|
for _, part := range parts {
|
|
if part != "" {
|
|
nonEmptyParts = append(nonEmptyParts, part)
|
|
}
|
|
}
|
|
|
|
return joinValues(nonEmptyParts)
|
|
}
|
|
|
|
// 格式化值
|
|
func formatValue(value interface{}) string {
|
|
if value == nil {
|
|
return "NULL"
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case string:
|
|
return fmt.Sprintf("'%s'", escapeString(v))
|
|
case []byte:
|
|
return fmt.Sprintf("'%s'", escapeString(string(v)))
|
|
default:
|
|
return fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
|
|
// 转义字符串
|
|
func escapeString(s string) string {
|
|
// 简单的转义,实际应用中可能需要更完整的实现
|
|
return s
|
|
}
|
|
|
|
// 连接值
|
|
func joinValues(values []string) string {
|
|
if len(values) == 0 {
|
|
return ""
|
|
}
|
|
|
|
result := values[0]
|
|
for i := 1; i < len(values); i++ {
|
|
result += ", " + values[i]
|
|
}
|
|
return result
|
|
}
|
|
|
|
// withRetry 重试操作直到成功或超过最大重试次数
|
|
func withRetry(operation func() error, maxRetries int) error {
|
|
var err error
|
|
for i := 0; i < maxRetries; i++ {
|
|
if err = operation(); err == nil {
|
|
return nil
|
|
}
|
|
log.Printf("操作失败,尝试重连 (%d/%d): %v", i+1, maxRetries, err)
|
|
time.Sleep(time.Duration(i+1) * time.Second)
|
|
}
|
|
return fmt.Errorf("超过最大重试次数: %v", err)
|
|
}
|
|
|