封装实验环境,助力观察MySQL binlog事件

维多利亚女王时代是聚积的年代;不仅仅是物质财富的聚积,而且是每一个国家能增强国力的所有因素与要素的增加和聚积。教育惠及社会各个阶层。科学打开大自然的无限宝库。宝库之门一扇一扇被打开。阴暗而神秘的宝库一个个被照亮,一个个被开发,任何人都可自由进入——每进入一个宝库,人们都会发现这个宝库至少又通向了另外两个宝库。每天早晨,人们一睁开眼,总能发现某种新机器开始了运转。每天晚上,当人们吃完晚饭之后,机器依旧在运转。在所有人进入梦乡之后机器也不会停止。 --《第一世界大战回忆录》温斯顿丘吉尔

背景

正如《第一次世界大战回忆录》中,丘吉尔对维多利亚时代的评述。人类的进步很大程度上依赖经验的传递与分享。这也是这片文章的目的,把笔者的一些经验积累沉淀下来,分享出去。

维护一个CDC模块是笔者工作(饭碗)的一部分,时不时会有同事问我一些相关的问题,比如:

  1. 如果我要执行这样的一个DDL,你的服务会突然接收到很多binlog事件吗?
  2. 如果我要改变一张表的编码格式,你的服务会收到很多binlog事件吗?

​ 人非圣贤,怎么可能知道这么多问题的答案呢?当然是需要验证嘛。本地的验证过程如下:

  1. 本地启动一个MySQL的服务,
  2. 跑一个之前写好的测试程序(最近公司组织换电脑了,直接原地爆炸)
  3. 然后执行上面说诸如DDL之类的变动,再观察程序打印出来的日志,
  4. 验证完毕。

​ 这样的验证方法也不能说有问题吧,唯一的槽点可能就是环境有时候可能不小心作一下就没了,或者遇到一些不可抗力因素,就乱了。所以我就再想,能不能搞成一个现成的实验环境,想用的时候用一下不用的时候放着就可以,沉淀下来。办法当然是有的啦(不然也不会有这篇文章了。)。

  1. 把这个实验环境需要的东西全部都打包成一个docker的image

  2. 再上传到docker hub

  3. 后面需要用的时候直接操作这个image就好,另外这个也算是分享出去了,别人也可以依赖我这个image去做一些东西。

  4. 然后弄一个操作文档(也就是这篇文章啦)操作这个image来观察binlog表现就好啦。

    说干就干,立马动身去找家星巴克,点上一杯咖啡,打开电脑。。。。

封装实验环境

让我们来梳理一下,这个实验环境里面应该有一个微型的CDC服务,也就是可以捕捉到binlog的变化,然后要有一个MySQL,让我们在上面操作想要做的数据库变动。好,Let‘s go,立马开干。

下面采用go-mysql这个库来捕捉binlog的变动,对于每一个binlog事件我们都打印日志来观察。

package main

import (
	"fmt"
	"github.com/go-mysql-org/go-mysql/canal"
	"github.com/go-mysql-org/go-mysql/mysql"
	"github.com/go-mysql-org/go-mysql/replication"
	"math/rand"
	"os"
	"time"
)

type MyEventHandler struct {
}

func (h *MyEventHandler) OnRotate(header *replication.EventHeader, rotateEvent *replication.RotateEvent) error {
	fmt.Fprintf(os.Stdout, "reccive a OnRotate event, and the header is %+v,the event is %+v\n", header, rotateEvent)
	return nil
}

func (h *MyEventHandler) OnTableChanged(header *replication.EventHeader, schema string, table string) error {
	fmt.Fprintf(os.Stdout, "reccive a OnTableChanged event, and the header is %+v schema is %s, table is %s\n", header, schema, table)
	return nil
}

func (h *MyEventHandler) OnDDL(header *replication.EventHeader, nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
	fmt.Fprintf(os.Stdout, "reccive a OnDDL event, and the header is %+v, the next position is %+v, queryEvent is %+v\n", nextPos, queryEvent)
	return nil
}

func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
	fmt.Fprintf(os.Stdout, "reccive a OnRow event, and the content of this event is %+v\n", e)
	return nil
}

func (h *MyEventHandler) OnXID(header *replication.EventHeader, nextPos mysql.Position) error {
	fmt.Fprintf(os.Stdout, "reccive a OnXID event, and the header is %+v, the next position is %+v\n", header, nextPos)
	return nil
}

func (h *MyEventHandler) OnGTID(header *replication.EventHeader, gtid mysql.GTIDSet) error {
	fmt.Fprintf(os.Stdout, "reccive a OnGTID event, and header is %+v, the gtid is %+v\n", header, gtid)
	return nil
}

func (h *MyEventHandler) OnPosSynced(header *replication.EventHeader, pos mysql.Position, set mysql.GTIDSet, force bool) error {
	fmt.Fprintf(os.Stdout, "reccive a OnPosSynced event, and the header is %+v, the position is %+v, gtidSet is %+v\n, force is %+v", header, pos, set, force)
	return nil
}

func (h *MyEventHandler) String() string {
	return "MyEventHandler"
}

func main() {
	cfg := new(canal.Config)
	cfg.Addr = "127.0.0.1:3306"
	cfg.User = "root"
	cfg.Password = "123456"
	cfg.ServerID = uint32(rand.New(rand.NewSource(time.Now().Unix())).Intn(1000)) + 1001

	c, err := canal.NewCanal(cfg)
	if err != nil {
		fmt.Fprintf(os.Stdout, "encounter a error during init canal, and the error is %s", err.Error())
		return
	}

	// Register a handler to handle RowsEvent
	c.SetEventHandler(&MyEventHandler{})

	// Start canal
	c.Run()
	select {}
}

通过下面这个Dockerfile分阶段构建我们的镜像,这里第一阶段把Go程序打包成一个可执行文件,然后第二阶段将这个文件拷贝进MySQL镜像中。

FROM golang as buildPrinterStage
WORKDIR /build
COPY ./main /build
RUN cd /build/main & go build -o binlog-printer

FROM mysql
ENV MYSQL_ROOT_PASSWORD=123456
COPY --from=buildPrinterStage /build/binlog-printer /
COPY schema.sql /schema.sql

另外,这里值得注意的是,第二阶段的构建除了拷贝了Go程序的编译产物,还拷贝了一个sql文件进去,这是为什么呢?因为MySQL镜像本身并没有测试数据,这个是我提供提供给用户的,如果你不想自己手动写任何DDL语句构建测试数据库,进入容器导入这个sql文件进MySQL是一个比较好的选择,贴心如我。sql内容如下:

-- 创建数据库
create database `docker_mysql` default character set utf8 collate utf8_general_ci;

use docker_mysql;

-- 建表
DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
                        `id` bigint(20) NOT NULL,
                        `created_at` bigint(40) DEFAULT NULL,
                        `last_modified` bigint(40) DEFAULT NULL,
                        `email` varchar(255) DEFAULT NULL,
                        `first_name` varchar(255) DEFAULT NULL,
                        `last_name` varchar(255) DEFAULT NULL,
                        `username` varchar(255) DEFAULT NULL,
                        PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

-- 插入数据
INSERT INTO `user` (`id`, `created_at`, `last_modified`, `email`, `first_name`, `last_name`, `username`)
VALUES
    (0,1490257904,1490257904,'[email protected]','Elliot','Chen','user');

这时候问题又来了,我的电脑是apple M1芯片,arm架构的,可能后面有朋友是amd架构的电脑,看到我这篇文章突然感兴趣了要玩一把怎么半?那岂不是跑不起来了?问题不大,我们可以直接编译成多平台的镜像上传。使用下面这个命令:

docker buildx build --platform linux/amd64,linux/arm64/v8 -t elliotchenzichang/binlog-exprienment-env . --push 

这时候打开我的docker hub。就可以看到,我只打包了amd64和arm64的镜像,相信大多数电脑都是64位的,应该足够了:

封装实验环境,助力观察MySQL binlog事件_第1张图片

好,我们把我的镜像拉下来:

docker pull elliotchenzichang/binlog-exprienment-env:latest

直接给他启动起来:

封装实验环境,助力观察MySQL binlog事件_第2张图片

哎哟,MySQL启动起来了哟,不错哈。让我们进入这个容器看看里面的情况,嗯?这不是我们的老熟人binlog-printer和schema.sql吗?

封装实验环境,助力观察MySQL binlog事件_第3张图片

这个时候可以开始执行binlog-printer开始捕捉binlog变动了。他会打印很多他以前留存下来的binlog,其实不用管这些,我们只需要知道这个是程序在正常运行,一切都ok。

接下来我们再打开一个窗口导入schema.sql到MySQL测试一下他能不能捕获到这些变动。输入我们预设的密码123456就好

image-20230417230723347

好,让我们切换回刚刚那个binlog-printer界面,这时候我们看到这里已经捕捉到了这些变动:

封装实验环境,助力观察MySQL binlog事件_第4张图片这个实验环境就搭建好啦。Perfect!

你可能感兴趣的:(mysql,数据库)