- SET @master_heartbeat_period=15
设置客户端与服务端心跳发送间隔,默认为15s。
Step2:从主库查询binlog checksum,具体向主库发送 select @@global.binlog_checksum 语句。
Step3:向MySQL Master 注册从节点,告知客户端的host、port、用户名与密码、serverId,具体实现是发送命令CODE为 0x15。
Step4:向 MySQL Master 发送 dump 请求,MySQL是基于请求与应答模式,发送请求命令后,就会向网络通道中写入响应请求。(在这里大家不妨先大概思考一下如何读取 dump 命令的返回值,这部分虽然涉及到网络相关的知识,我在这边会稍微简单提一下)。
Step5:构建 DirectLogFetcher对象,实现基于 socket 的日志拉取服务,并构建 LogDecoder 对象,用于解析 binlog 日志。
Step6:使用 while 循环反复拉取消息,通过通过 LogDecoder 对二进制流进行解析,提取一条完整的binlog事件,交给 SinkFunction 去处理,并且如果开启了半同步机制,则需要向master发送ACK。既然是while循环,该方法的退出条件还是值的我们关注的:
-
fetch.fetch()方法返回 false
-
SinkFunction 的 sink 方法 false,SinkFunction的详细处理流程将在下文介绍,这里先告知返回false的情况是 binlog 日志解析线程已停止运行。
上面粗略的介绍了 dump 命令的几个核心关键步骤,要想详细掌握其实现细节,我们必须继续深入探讨如下几个问题:
-
DirectLogFetcher 内部工作机制
-
LogDecoder binlog 日志解析
-
发送Dump底层网实现思路
2.1 DirectLogFetcher 类图
DirectLogFetcher的类继承体系如上图所示,我们来看一下其关键点:
- LogBuffer
日志buffer,主要定义如下属性:
- byte[] buffer
缓存区中数据容器。
- int origin
当前buffer中的读指针
- int limit
当前buffer的最大可读可写指针
- int position
当前buffer的写指针。
- int semival
是否需要发送ACK(用于半同步)。
LogBuffer封装了字节相关的操作,不仅定义了上面的属性,也定义了字节读取相关众多API,其截图如下:
- LogFetcher
binlog日志抓取抽象类,定义了如下关键属性与抽象方法。
- int DEFAULT_INITIAL_CAPACITY
LogBuffer中的初始容量,默认为8K。
- float DEFAULT_GROWTH_FACTOR
容量增长因子,默认为 2.0。
- int BIN_LOG_HEADER_SIZE
binlog日志条目 header 的长度,固定为4字节。
- float factor
增长因子。
- public abstract boolean fetch()
抓取binlog日志。
- public abstract void close()
关闭 Fetch。
- DirectLogFetcher
Canal LogFetcher模式实现类,其核心属性如下:
- SocketChannel channel
网络通道,用于发送dump请求的网络通道。
- boolean issemi = false
是否开启半同步。
2.2 fetch流程详解
接下来我们重点剖析 DirectLogFetcher 的 fetch 方法,来探究其实现原理。
在研究DirectLogFetcher的fetch方法之前,我们先重点跟踪一下其内部网络读写方法fetch0方法,该方法是具体与网络读写相关的实现。
在详细介绍该方法之前先来介绍一下其参数的含义:
- int off
从通道中读取到的内容放入到buffer中的起始位置
- int len
期望从通道中读取的字节长度。
该方法的实现关键点如下:
-
首先先确保接收缓存区有足够的剩余空间,如果空间不足,则进行扩容。
-
然后从通道中读取指定长度的字节。
接下来我们来重点看一下DirectLogFetcher的fetch的实现流程。
Step1:尝试从网络通道中读取4个字节(即读取协议的头部),如果通道中还没有可读取内容,返回false,造成的效果是一次 dump 请求结束。
Step2:从上文读到的4个字节分别读出该网络包的总长度以及当前包的序号,从这里可以看成MySQL协议头为4字节,前3个字节为网络包的总长度,第4个字节为包的序列号。再取出数据包的长度后,继续向通道中读取netlen个字节,即读取一个完整的数据包到buffer中。
Step3:继续从数据包中读取一个字节,判断该包的状态码,是否是一个成功的响应,如果是错误的响应,会向外抛出一次,Canal 会记录dump命令执行错误的次数。
Step4:如果一个包的长度为允许的最大包长度,则继续读取,这个主要是根据MySQL协议做的处理,即读取到一个数据包,然后返回true,表示拉取到一条日志,然后通过LogDecoder解码,然后传入到sink方法中,进行日志的后续处理。
Step5:这一步的目的,就是将buffer中的当前指针指向数据的开始位置。这样一次 fetch就结束了。
从上面的流程来看,DirectLogFetcher#fetch 方法结束后,就将进入到LogDecoder中。经过一次DirectLogFetcher#fetch方法后,即取回一条binlog日志,即二进制流,接下来就根据binlog协议对其解析。本文暂不深入该方法,如果大家想深入数据库中间件方面,可以作为一个很好的示例,面向MySQL通信协议进行编程。
最后
看完美团、字节、腾讯这三家的面试问题,是不是感觉问的特别多,可能咱们又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。
开篇有提及我可是足足背下了1000道题目,多少还是有点用的呢,我看了下,上面这些问题大部分都能从我背的题里找到的,所以今天给大家分享一下互联网工程师必备的面试1000题。
注意不论是我说的互联网面试1000题,还是后面提及的算法与数据结构、设计模式以及更多的Java学习笔记等,皆可分享给各位朋友
互联网工程师必备的面试1000题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。
(img-dNJSr9sr-1714280230589)]
互联网工程师必备的面试1000题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。
[外链图片转存中…(img-h9l4wpAE-1714280230590)]