MYSQL 同步到 ES 如何设计架构保持一致性
简单使用某个组件很容易,但是一旦要搬到生产上就要考虑各种各样的异常,保证你方案的可靠性,可恢复性就是我们需要思考的问题。今天来聊聊我们部门在 MYSQL 同步到 ES 的方案设计。
在面对复杂条件查询时,MYSQL 往往显得力不从心,一般公司的做法会通过将 mysql 中的数据同步到 ES,之后的查询就通过 ES 进行查询,ES 在面对多条件复杂查询时,能较快的查询出结果集。
在 MYSQL 数据 到 ES 中的数据同步 方案设计上,就有多种选择,
1,最简单的便是直接在业务代码中对数据库进行修改,插入,删除时,同步修改 ES 中的数据。 但这种方案也是最不可靠的一种设计
。在写入 MYSQL 后,业务服务宕机了,ES 数据就会丢失。如果写入 ES 失败,重试逻辑将会嵌套在业务代码中,业务代码复杂性增加了,并且如果一直失败,要一直重试吗?
所以,对于这种方案,直接 pass 掉了。
2,第二种同步方案则是业界用的比较多的同步方案,通过 binlog 进行同步,目前业界已经有比较成熟的模拟 mysql 从库,拉取 binlog 的组件,例如阿里开源的canal
。整个同步架构如下所示,canal 组件充当 mysql 从库的角色,将 mysql 的 binlog 拉取下来,由客户端从 canal 拉取消息进行消费,再由客户端主动插入或者更新 ES 中的数据。
📢📢注意,这里我用的是客户端主动从 cannal 拉取 binlog 消息的方式,实际上还可以通过让 cannal 主动发送 binlog 消息到消息队列,然后 client 异步消费 kafka 中的消息。
所以,我们部门选择了第二种同步方案,并且由于数据量并不大,也没有接入 kafka,直接采用客户端从 cannal 侧拉数据的方式。
canal 同步数据异常情况分析
接着我先来提几个问题,我们可以思考🤔看看当前的架构设计是否能满足数据同步的基本要求。
1,如果客户端消费数据失败,会造成数据丢失吗?
2,如果 cannal 崩溃了,那么会造成拉取的 binlog 没有被消费而造成数据丢失的情况吗?
3,canal 会有重复推送消费消息的情况吗?
4,如果 ES 侧暂时宕机,要想不丢失数据,应该怎么做?
以上是我针对同步数据时 围绕这个设计方案的可靠性与可恢复 提出的几个问题,我们针对它们来看看同步数据应该如何来做。
第一个问题, 客户端如果从
canal拉取到了消息,但是本地由于异常,或者宕机 导致消费失败了,可以做到不丢失数据
。因为canal
对于消息的消费模型提供了ACK
机制,客户端在拉取完一批消息后,可以依次消费消息,然后发送对应消息的 ACK,如果消费失败,或者本地宕机,那么下次拉取消息的时候依然能够拉取到没有消费完的消息。
第二个问题, cannal 如果异常崩溃,也是可以做到 消息不丢失
,canal 在从数据库拉取 binlog 时,会记录拉取的日志偏移量 offset 到内存,但是偏移量的持久化 其实是通过定时任务 考虑客户端 ACK 位点后,才进行记录的,可以选择记录到 zookeeper 或者本地文件。所以如果 canal 宕机了,那么重启后,会从 zookeeper 或者本地文件中读取客户端最后 ack 的位点,然后从这个位置开始从数据库拉取消息。为了让 canal 快速恢复,还可以做 canal 集群,让集群中始终有备节点。
第三个问题,canal的确会有重复推送消费消息的可能
,正如第二问题说的那样 偏移量的持久化是通过定时任务记录的,所以存在客户端消费了消息,但是这个 ack 位点还没有持久化的情况,如果这个时候 canal 宕机重启了,那么将会把客户端消费过的消息也再发一遍。所以客户端消费消息需要做幂等处理。
第四个问题, 如果ES侧的数据写入失败了,或者ES直接宕机,也是能够做到ES宕机恢复后,数据不丢失的
,最简单的方式其实是客户端发现 ES 写入失败了,然后不 ACK 消息,直接不断重试,直到写入成功为止,不过这种做法其实不太好,因为不 ACK,那么消息会一直存到canal
的内存里,同时canal
会不断 dump 数据库的 binlog 日志,又塞到内存里等待被客户端拉取消费,这样造成的后果就是canal
的内存会越来越大,最终停止数据库的同步操作。
一个比较好点的方式是,客户端消费了canal
的消息,直接在本地将消息保存起来,比如写入到磁盘文件上,写入成功后即可发起 ACK,然后本地启一个协程慢慢将磁盘文件上的消息更新到 ES 中,所以,最后选用了 leveldb 对做本地文件的写入,leveldb 写入文件的操作是很快的,这样快速的 ACK 消息对canal
的内存压力也会小很多。
综上,我们部门的数据同步模型就变成了下面这样,canal 做了集群,保证宕机了还有其他节点可以继续同步,客户端则是消费消息后将先把消息写入到本地,然后开启定时任务写入到 ES,如果有错误的话,会一直重试,直到成功为止。
总结
最后,我来总结下,采用canal
去做 MySQL 到 ES 的数据同步,我们的确是可以做到高可靠性的,但是要注意的canal
的消息消费是有可能出现重复消息的,不过由于目前我们部门没有对消息进行统计的需求,仅仅是将数据进行更新或者插入,存在即更新,没有即插入,所以是幂等,可以不用太过关注。
文章转载自:蓝胖子的编程梦
评论