背景
丰巢作为一家快速发展的科技公司,在我们平时的工作中,有很多的数据库迁移、改造、测试等工作。在我们之前做的一次异构数据库迁移过程中,我们投入了相对较多的人力。我本人一直在思考如何投入最少的人力,并且能使这个过程更加安全稳定,其中一个技术难点便是对于生产环境的流量在测试环境进行持续的回放,并在这个过程中发现问题所在。
技术选型
一开始本着不重复造轮子的原则,想在开源领域找到一款合适我们的产品。主要调研了tcpcopy框架,但是它不能满足我司的实际情况:
- 我们后台服务使用的都是长连接技术,tcpcopy只有等到下一次连接登录时才能完成实际的链路创建,这个在我们的环境中完全行不通,我们需要所有录制的信息都可以100%被回放;
- 我们有很多的场景,库名和表名的映射都发生了变化,tcpcopy等现有技术无法解决我们的这个问题;
- 我们希望在录制和回放的过程中,不但能够校验是否发生错误、响应延迟等,还能对response做校验,甚至是对两个数据库的数据一致性做校验;
为了满足上面3个要求,我们开始了自研之路。在开始之前,我们需要一款能从数据链路层抓包的框架,我们选择了google开源的gopacket,因为我们之前使用gopacket实现过redis的实时命令分析工具,因此对它非常有信心。
功能分解
我对这款工具的定位是可以持续的输出业务价值,因此我希望它能最短的时间里便能产生作用,在制定第一期的功能列表时,本着不求大而全,只求有用的原则:
- mysql协议理解:目前版本中只需理解登录、命令执行和连接关闭即可,非常简单;
- 命令解析及输出:直接输出实际的sql文本命令,命令会标识出具体是哪个连接的命令及发送的时间;
- 回放功能:支持原速回放和倍速回放;
- 延迟和错误检测;
技术实现
gopacket使用
gopacket的使用非常简单,只需要在go.mod中引用gopacket的最新版本即可:
require github.com/google/gopacket v1.1.17
代码示例:
devices, err := pcap.FindAllDevs()
if err != nil {
log.Fatal(err)
}
for _, deviceGet := range devices {
for _, address := range deviceGet.Addresses {
if address.IP.String() == *ip {
device = deviceGet.Name
break
}
}
}
if device == "" {
fmt.Println("the ip don't match the network device. maybe you don't have the sudo authority.")
return
}
handle, err = pcap.OpenLive(device, snapshot_len, true, timeout)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
var filter string = "tcp and dst host "
filter = filter + *ip + " and dst port " + strconv.Itoa(*port)
err = handle.SetBPFFilter(filter)
if err != nil {
log.Fatal(err)
}
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
var origin []byte
for packet := range packetSource.Packets() {
//do something
}
倍速实现
关于回放逻辑中的倍速实现,我们需要知道几个参数:流量录制时的开始时间recordB、结束时间recordE、命令的执行时间recordC、流量回放时的开始时间replayB,通过这些参数我们就可以推断出命令在命令在回放时的实际执行时间replayC,公式如下,speed为放大的倍数,默认为1:
replayC = (recordC+(replayB-recordB)-replayB) /speed + replayB
效果
大家也可以看出,我们在第一期 实现中,逻辑都比较简单,开发时间也就只花了5天左右的时间,我们使用此工具对我司支付平台的生产环境mysql流量进行了录制,并在测试环境中的TiDB上进行了回放,共发现了两个问题:
- TiKV的一个已经解决的bug,我司目前tidb的版本是2.1.9,当transfer leader时遇到了conf change时,会出现短暂的慢查询现象(连接会被TiDB占住一段时间)PR详见;
- nginx在做tcp proxy时,会偶尔出现连接卡死的现象,只有到达超时时间,连接被kill后,此连接的业务才会恢复。我司之前使用的是第三方的nginx_tcp_proxy_module的模块。后来把nginx的版本升级为1.17.1并使用nginx官方的stream模块,此问题也解决了;
未来
我对这个工具还是很期待的,我希望它能够在资源可控的条件下,实时跑在生产环境mysql的实例服务器上,总结一下后续的计划:
- 可以边录制、边回放;
- 可以对CRUD语句进行校验检测;
- 可以定期的对于目标表数据进行一致性的校验;
- 可以不仅仅支持mysql的协议;
- 可以开源出来;
- ...