利用MySQL的Binlog实现数据同步与订阅(上):基础篇
利⽤MySQL的Binlog实现数据同步与订阅(上):基础篇
终于等到了周末,在经历了⼀周的忙碌后,终于可以利⽤空闲写篇博客。其实,博主有⼀点困惑,困惑于这个世界早已“堆积”起⼈类难以想象的“⼤”数据,⽽我们⼜好像执着于去“造”⼀个⼜⼀个“差不多”的“内容管理系统”,从前我们说互联⽹的精神是开放和分享,可不知从什么时候起,我们亲⼿打造了⼀个⼜⼀个的“信息孤岛”。⽽为了打通这些“关节”,就不得不去造⼀张巨⼤⽆⽐的蜘蛛⽹,你说这就是互联⽹的本质,对此我表⽰⽆法反驳。我更关⼼的是这其中最脆弱的部分,即:⼀条数据怎么从A系统流转到B系统。可能你会想
到API或者ETL这样的关键词,⽽我今天想说的关键词则是Binlog。假如你经常需要让数据近乎实时地在两个系统间流转,那么你应该停下来听我——⼀个不⽢⼼整天写CRUD换取996福报的程序员,讲讲如何通过Binlog实现数据同步和订阅的故事。
什么是Binlog
⾸先,来回答第⼀个问题,什么是Binlog?Binlog 即 Binary Log,是MySQL中的⼀种⼆进制⽇志⽂件。它可以记录MySQL内部对数据库的所有修改,故,设计Binlog最主要的⽬的是满⾜数据库主从复制和增量恢复的需要。对于主从复制,想必⼤家都⽿熟能详呢,因为但凡提及数据库性能优化,⼤家⾸先想到的所谓的“读写分离”,⽽⽆论是物理层⾯的⼀主多从,还是架构层⾯的CQRS,这背后最⼤的功⾂当
属主从复制,⽽实现主从复制的更底层原因,则要从Binlog说起。⽽对于数据库恢复,⾝为互联⽹从业者,对于像“rm -f”和“删库”、“跑路”这些梗,更是喜闻乐见,⽐如像今年的绿盟删库事件,在数据被删除以后,⼯程师花了好⼏天时间去抢救数据,这其中就⽤到了Binlog。
怎么挽救婚姻可能⼤家会好奇,为什么Binlog可以做到这些事情。其实,从Binlog的三种模式上,我们就可以窥其⼀⼆,它们分别
是:Statement、Row、Mixed,其中Statement模式记录的是所有数据库操作对应的SQL语句,如INSERT、UPDATE 、DELETE 等DML 语句,CREATE 、DROP 、ALTER 等DDL,所以,从理论上讲,只要按顺序执⾏这些SQL 语句,就可以实现不同数据库间的数据复制。⽽Row模式更关⼼每⼀⾏的变更,这种在实际应⽤中会更普遍⼀点,因为有时候更关⼼数据的变化情况,例如⼀个订单被创建出来,司机通过App接收了某个运输任务等。⽽Mixed模式可以认为是Statement模式和Row模式的混合体,因为Statement模式和Row模式都有各⾃的不⾜,前者可能会导致数据不⼀致,⽽后者则会占⽤⼤量的存储空间。在实际使⽤中,我们往往会借助各种各样的⼯具,譬如官⽅⾃带
的mysqlbinlog、⽀持Binlog解析的StreamSets等等。
psv游戏好了,下⾯我们简单介绍下Binlog相关的知识点。在使⽤Binlog前,⾸先需要确认是否开启了Binlog,此时,我们可以使⽤下⾯的命令:
2022立秋时间
SHOW VARIABLES LIKE 'LOG_BIN'
如果可以看到下⾯的结果,则表⽰Binlog功能已开启。
如果Binlog没有开启怎么办呢?此时,就需要我们⼿动来开启,为此我们需要修改MySQL的my.conf⽂件,通常情况下,该⽂件位
于/etc/myf路径,在[mysqld]下写⼊如下内容:
# 设置Binlog存储⽬录
京东商城广告
log_bin                      =    /var/lib/mysql/bin-log
# 设置Binlog索引存储⽬录
log_bin_index              =    /var/lib/mysql/mysql-bin.index
# 删除7天前的Binlog
expire_logs_days          = 7
# 集内MySQL服务器的ID
server_id                = 0002
# 设置Binlog⽇志模式
binlog_format              = ROW
除此之外,我们还可以设置下⾯这些选项:
# 设置Binlog⽂件最⼤的⼤⼩
max_binlog_size
# 设置当前多少个事务缓存在内存中
binlog_cache_size
# 设置当前多少个事务暂存在磁盘上
binlog_cache_disk_use
# 设置最⼤有多少个事务缓存在内存中
max_binlog_cache_size
# 设置选取或者忽略的数据库
binlog_do_db/binlog_ingore_db
设置完以后,通过下⾯的命令重启MySQL即可:
service mysql restart
或者
service mysqld restart
通常,我们可以通过下⾯的命令来获取Binlog的当前状态,请注意,该命令必须要在主库上执⾏:
SHOW MASTER STATUS
此时,我们会得到下⾯的结果:
这⾥可以得到三个重要的信息,即从⽇志⽂件mysql-bin.000388的特定位置135586062开始,可以获得⼀组新的⽇志信息,⽽这些⽇志信息都是来⾃数据库实例b1328d03-0b5c-11ea-8ee8-005056a1616f:1-27768340。有了这三个信息以后,我们就可以去查看对应的BinLog,此时,我们需要使⽤到下⾯的命令:
SHOW BINLOG EVENTS IN 'MYSQL-BIN.000388' FROM 135586062
此时,ROW模式下的Binlog如下图所⽰:
可以注意到,这些Binlog由不同的事件构成。如果你是在MySQL终端下输⼊命令,那么,你还可以使⽤官⽅⾃带的⼯具mysqlbinlog,博主这⾥使⽤的开源的数据库⼯具,如果你经常需要和不同的数据库打交道,⽽⼜不想每⼀种数据库都去安装⼀个客户端的话,我认为这是⼀个⾮常不错的选择。关于Binlog的使⽤我们就先暂时说到这⾥,因为还有更重要的事情要做。
Binlog有什么⽤
实现数据库审计
实现事件驱动
其次,我们在实际业务中,常常需要⽤到"领域事件"这个概念,即使项⽬并没有采⽤**领域驱动设计(DDD)**的思想,即使项⽬中并没有采⽤”事件驱动“的业务模式,可事实就是,总有⼈关⼼着数据的产⽣和变更,⽽能提供给第三⽅系统订阅⾃⼰感兴趣的事件的能⼒,⽆疑要⽐开发⼀个⼜⼀个⼤同⼩异的同步接⼝要好得多,推(Push)模式在⼤多数情况下要⽐拉(Pull)模式要好,为什么呢?因为数据传输的压⼒更⼩,更能满⾜数据实时性的要求。然⽽,由于没有按照领域模型去设计业务,导致事件代码与业务代码耦合⾮常严重,基于Binlog的事件分发机制显然有更好的普适性。以博主最近处理的业务为例,A系统中的司机、设备、⽤户在新建/更新更新时,需要把新数据推送到B系统,因为这类纯数据类的"变化"没有实际业务意义,所以,⼈们不舍得为这些变化去分发事件,⽽要想分发事件,⼜不得不去⾯对强耦合带来的阵痛,所以,Binlog的第⼆个⽤途是可以作为事件源来实现事件驱动。
业内主流⽅案
如果你觉得通过第⼀节的内容,可以⾮常容易地实现Binlog的解析,那么,我觉得你并没有想清楚Binlog处理过程中的难点在哪⾥?⾸先,每次读取Binlog,必须要知道对应的⽇志⽂件和位置,⽽如果在新的Binlog 产⽣前,没有处理完原来的Binlog,就必须要记录对应的⽇志⽂件和位置,⽽且经过博主本⼈测试,Binlog⽆法直接给查询语句追加过滤条件,来达到筛选某些数据库、表以及事件的⽬
的,⽽且⽇志⽂件的格式会因为模式的不同⽽不同,最主要的⼀点是,直接在主库上读取Binlog会给数据库带来访问压⼒,所以,主流的⽅案,是让客户端伪装成“从库”,关于⼀点,我们可以配合下⾯的图⽚来理解。
可以注意到,完成主从复制需要⼀个Relaylog + 两个线程,即,主库产⽣的Binlog,⾸先由从库的I/O线程进⾏读取,这⼀步会产⽣Relaylog,顾名思义,这是⼀个处在中间状态的中继⽇志,⽽中继⽇志最终会交由从库的SQL线程来处理,所以,这是从库执⾏SQL语句的阶段,整个过程是异步化的操作,所以,不会对主库产⽣太⼤的压⼒。如果我们直接读取主库的Binlog,实际上是把所有压⼒都转移到主库,不仅需要负责“读”,还需要复杂“写”。主流的⽅案,⽬前⽐较推荐的是阿⾥的、Zendesk的、以及来⾃社区的,下⾯是⼀个简单的对⽐,⽅便⼤家做技术选型。
Canal Maxwell Python-Mysql-Rplication 开源⽅阿⾥巴巴Zendesk社区
开发语⾔Java Java Python
活跃度活跃活跃活跃
⾼可⽤⽀持⽀持不⽀持
客户端Java/Go/PHP/Python/Rust
⽆Python
消息落地Kafka/RocketMQ等
教育培训工作总结
Kafka/RabbitNQ/Redis等
⾃定义消息格式⾃定义JSON ⾃定义⽂档详略详细详细详细Boostrap
不⽀持
⽀持
不⽀持
Canal
word方框内打钩Maxwell
Python-Mysql-Rplication
说说我的构想
众所知周,我是⼀个有⼀点“懒惰”的⼈,考虑到前⾯两种⽅案都⽐较重,即使通过Docker来安装。对我来说,这是⼀个验证想法的过程,所以,我选择的搭配是RabbitMQ + .NET Core + Python的⽅案,因为Kafka需要ZooKeeper,⽽在验证想法的阶段,⾃然是越简单越好。我正打算参考微软的eSh
opOnContainers的项⽬, 实现⼀个消息总线(EventBus),恰好这个项⽬中使⽤了RabbitMQ,⽽且从某种意义上来说,RabbitMQ更接近传统意义上的消息队列,它提供的重试、确认、死信等这些机制都⽐较完善,可以让我把精⼒集中在快速实现上,毕竟你看到这些博客,都是我挤出时间来完成的。选择Python就更直接了,因为安装、运⾏都⾮常容易,或许Kafka的吞吐性能更好,但我觉得掌握核⼼思想才是最重要的吧!
总⽽⾔之,在这⾥,我选择了⾃⼰最熟悉的技术栈。整体思路是,⾸先,.NET Core + RabbitMQ 实现⼀个消息总线,并对外提供发布事件的API接⼝。其次,利⽤Python-Mysql-Replication实现⼀个读取Binlog的后台程序,这些Binlog最终会以JSON的形式发布到RabbitMQ上。最后,实现针对特定事件的IEventHandler接⼝,消息总线会⾃动调⽤这些Handler去处理消息。⾄此,就实现了针对Binlog的订阅和消费。众所周知,消息总线的⼀⼤优点就是解耦,我们就可以摆脱以往定时轮询 + 打标记(Flag)的宿命轮回,只需要编写对应的Handler即可,其实我觉得这是⼀种思维上的转变,就是"主动"到"被动"的转变,并不是说我们帮客户做得越多越好,⽽是我们能让客
户意识到它可以做哪些事情。同样的,我绘制了⼀个简单的流程图来作为说明:
本⽂⼩结
其实,重复的⼯作做久了都会感到厌烦的,所以,真正让你摆脱“体⼒劳动”的只能是换⼀种⾼度来看问题。这⼏年做2B业务下来,最⼤的体会是企业级软件最难的是,如何在各种种类繁多的软件,譬如OA 、⾦蝶、⽤友、SAP 、ERP 、CRM等中做好⼀个“配⾓”,数据如果⽆法在这张⽹络中流通,则永远都是⼀潭死⽔,⽽如果要打通各个系统间的数据,则免不了写⼀个⼜⼀个的同步接⼝。这篇博客以
MySQL的Binlog为切⼊点,试图通过Binlog来实现特定业务的“事件驱动”。Binlog是实现主从复制的重要机制,⽽基于这⼀机制,业界普遍的做法是利⽤MySQL的交换协议,让客户端"伪装"成⼀个从库,在⽐较了Canal 、Maxwell 以及Python-Mysql-Replication后,博主选择了. NET Core + RabbitMQ + Python的⽅案,⽬标是让Binlog可以发布到消息总线(EventBus)中供消费者订阅和消费。在下⼀篇博客中,我们讲介绍基于RabbitMQ实现⼀个消息总线(EventBus)的相关细节,欢迎⼤家继续关注我的博客,今天这篇博客就先写到这⾥,⼤家晚安!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。