背景
在之前的文章中有提到过,维护一个CDC模块是我工作 ( 饭碗 ) 的一部分, 所谓的CDC呢,就是Change Data Capture, 变化数据捕捉,他能捕捉到类似MySQL这样的存储模块的数据变化,并且发送到别的地方,比如Kafka,ElasticSearch。在这个领域内比较成熟的开源项目有Debizium,阿里的Canal,Flink-CDC。我们的是用Go开源的框架 go-mysql 提供的 CDC 功能搭建的平台。最近呢,这个平台又开始作妖了。
发生了什么事情呢?让我来抽象一下这个case,比如其实你的数据库中有30张表,但是你只关注其中的20张表的数据变动,但是这20张之外的一些表的数据变动量如果是这20张的10倍。你该怎么办。这时候你会说了,MySQL可以配置只写某些表的binlog,这个问题很轻松就可以解决啦。是的,不过很明显,因为一些原因,我们没有做这样的配置。我们正是面临着这样的问题,这个问题导致我们这个服务的内存被打趴下,同步数据的 lag 也大大延长了。
作为技术狂魔的我,当然会开始研究研究这个东西啦。除了追踪这些数据变动的来源之外,我当然会探究一下有没有一些技术性的解决方案来解决这个问题啦,毕竟这样可以一劳永逸。这片文章记录下我的探究结果,以及一些个人的想法。不当之处还请指教。
为什么会变慢呢?
这里首先要向大家解释一下MySQL CDC一般实现是怎么样的,一般来说MySQL的数据变动都会写进一个叫binlog的东西里面,然后MySQL的主从同步实际上就是同步这个binlog,那么很显然,CDC服务就是把自己伪装成一个MySQL的从属节点,构建长链接,然后让MySQL向你同步数据。
一开始我以为问题是因为我们收到了我们不需要的表的变动,占用了大量的TCP缓存。才导致的内存变高。但是细想之下,这个想法其实是力不足脚的。因为TCP的buffer的生命周期是,从发送端收到这个packet,到应用层读取这个packet,换而言之,等到我们的程序从Linux内核读取了这个packet,这个buffer就可以被回收了。 所以关键的地方是我们读取了这个东西,然后造成了内存高涨,并且一直居高不下,因为一直收到那么多MySQL的变更事件,即便是 go 的 GC 看到这种情况也有点有心无力。
深入看这个开源项目的源码实现就会发现 (源码太长,我这里就不展示了), 他们并没有针对一些table做特殊处理,无论什么数据都会decode出来。虽然我们在程序中会判断是哪张table的变更,如果不是我们关注数据变更的就不要了。但是走到这一步的时候他已经帮我们把所有数据都decode出来了,剩下的就是把所有东西都摆在餐桌上供我们挑选。
如何解决这个问题呢?
这个时候我很自然的想到了,如果可以在这个开源项目内部就不解析我们不关注的表,那不就完事了嘛,那这片内存的生命周期最多就到从网络空间里拿出来,然后再到这个判断里面,如果用户配置了不需要这个东西,那就拜拜了您嘞。多么美妙啊。
于是我直接去提issue了。想问下可不可以直接不关注一些table的变更,如果不行的话,我找个业余时间给他们贡献一下也是可以的。
因为之前给也给这个项目提过issue,知道对面是个中国人,所以直接用中文提了,但是这个老哥第二天回复我让我翻译成中文,hhhhh。
他的回复大致意思是这样,他们之前也遇到过这样的场景,所以做了一个东西,rowsEventDecodeFunc,这是一个回调函数,允许用户自定义如何处理所有接收到的变更。另外他给我附上了他们的一个数据同步项目 tiflow(没错,就是tidb的数据同步项目)的一个使用案例。
这个case的大致意思是如果这个表在blocklist里面,就不decode出来了。这个正好是我想要的效果。其实到这里这个问题基本上就解决了。
我的一些想法
但是我想提一下我的一些想法,很明显,给用户开放这样的一个回调函数,我感觉太重了,如果看不到这个使用的case,我都不知道怎么用他。比较好的做法我感觉可能是需要在这个基础上再封装一次。给用户一些比较清晰的语意配置。这个我有时间想做做,给他们提个PR,岂不是美滋滋?
另外这个只是针对了一个场景,也就是说用户并不想处理所有数据库的变更的场景。但是用户如果想处理所有数据,但是内存不够怎么办?很明显CDC平台是IO密集型的服务,从数据库接受信息,然后稍微处理一些转发给下游。一般这种情况下CPU的使用率不会很高。所以我感觉这种场景下,可以加一条本地的队列,这个队列的信息是存在磁盘里的,先把从数据库来的数据写入到磁盘里,然后读出来消费。另外需要一个第三方的东西记录你的处理到的数据。因为如果服务崩溃了,你需要这个位置做恢复。虽然这样会慢一些,但是CDC的平台其实对 lag 要求不是很高。这个是我比较放飞的想法啦,可能会存在很大的问题,只是分享给大家看看,大家看看笑笑就好。
当然最治本的方法是让MySQL只写那几张你关注的表的binlog。可以开一个专门的replica去做这件事。这个架构就会变成这样。
总结
好了,感谢你还看到最后,这里分享了一下在工作上对一些问题的想法和探究过程。希望你看了之后有所收获。感谢。