MySQL 如何实现将数据实时同步到 ES ?

引言

在日常的开发中,我们一般会使用 MySQL 来作为数据的存储,然后 ES 来实现全文的数据检索以及特殊查询,那么这个时候就会有一个问题,我们 MySQL 如何实时将数据同步到 ES 中呢?我们接下来来盘点一下。

解决方案

数据双写

这个方案应该是比较常用的,即在写入数据的时候,先将数据写入 MySQL 然后在将数据写入 ES,这种方案实现起来比较简单,但是如果你在写入 MySQL 之后,服务发生了宕机,这个时候就可能产生不一致的情况,这个时候就可能需要重新进行写入。

image.png

伪代码如下:

text
复制代码
/** * 新增商品 */ @Transactional(rollbackFor = Exception.class) public void addGoods(GoodsDto goodsDto) { //1、保存Mysql Goods goods = new Goods(); BeanUtils.copyProperties(goodsDto,goods); GoodsMapper.insert(); //2、保存ES IndexRequest indexRequest = new IndexRequest("goods_index","_doc"); indexRequest.source(JSON.toJSONString(goods), XContentType.JSON); indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); highLevelClient.index(indexRequest); }

这个方案的优缺点如下:

  • 优点:
    • 实现起来逻辑简单,而且实时性较高
  • 缺点:
    • 硬编码问题严重,并且业务耦合程度高
    • 如果服务或者 Elasticsearch 发生宕机情况,就有数据丢失的风险

MQ 异步同步

这个思路应该是大家比较容易想到的,就是在执行完 MySQL 的写入操作之后,将操作交给 MQ,然后通过 MQ 告诉 ES 需要进行数据的同步。

image.png 这个方案优缺点如下:

  • 优点:
    • 这个方案最直接的点就是性能高,并且实现了业务的解耦合,并且可以利用 MQ 的重试机制,在写入失败的时候进行重试,降低了数据丢失的风险。
    • 这样还支持多个数据源的写入,提高了扩展性,不会出现由于单个数据源写入异常从而导致其他数据源写入受到影响的问题。
  • 缺点:
    • 硬编码问题,在接入新的数据源的时候需要实现新的消费者代码,代码侵入性较强
    • 引入了消息队列,提高了运维的成本,增加了系统的复杂程度
    • 可能出现延时问题,因为消息队列是异步消费模型,用户写入的数据不一定可以马上看到结果,有一定的延迟。

基于 Binlog 实现数据同步

上面的这两个方案总结下来就两个问题,第一个问题就是硬编码并且代码侵入性较强,另外一个点就是没有办法实现数据的实时同步,那么有没有其他方案,答案肯定是有的,那就是利用 MySQL 的 Binlog 日志来实现数据同步,如下图所示:

image.png 具体步骤如下:

  • 优点:
    • 没有代码侵入,没有硬编码
    • 原有的系统没有任何变化,可以实现无感知,性能较高
    • 业务解耦合,这个和消息队列是差不多实现思路的,不过这个不需要关注原来系统的业务实现
  • 缺点:
    • 如果采用 MQ 消费解析 Binlog 日志的话,又会回到方案二引入消息队列产生的问题

针对以上方案,我们可以使用一种新的解决方案,就是阿里巴巴开源的 Canal 中间件。

Canal 方案

什么是 Canal ?

Canal:译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。

image.png

说白了就是,根据 MySQL 的 BinLog 日志进行增量同步数据。要理解 Canal 的原理,就要先了解 MySQL 的主从复制原理,如下:

  1. MySQL 所有的增删改操作都会进入MySQL 主从复杂的主节点,即 Master 节点。
  2. Master 节点会生成 Binlog 日志文件,每次操作 MySQL 数据库就会记录到 Binlog 日志文件中。
  3. Slave 节点会订阅 Master 节点的 Binlog 日志,以增量备份的形式同步数据到 Slave 数据。

Canal 同步流程

Canal 的原理就是伪装成 MySQL主从复制的从节点(Slave 节点),从而订阅 MySQL主节点的 Binlog日志,主要流程如下:

  1. Canal 服务端向 MySQL 的 Master 节点传输 Dump 协议
  2. MySQL 的 Master 节点接收到 Dump 请求后推送 Binlog日志给 Canal 服务端,解析 Binlog 日志对象(原始为字节流对象),然后转换成 JSON 数据格式
  3. Canal 客户端通过 TCP 协议或 MQ 形式监听 Canal 服务端,然后将同步数据到ES,到此,同步过程就完成了。

总结

  1. 数据双写是最简单的实现方式,可以最大程度的保证数据的同步写入,不过问题很明显,就是代码侵入性太强了,而且容易因为中间件故障导致写入失败。
  2. MQ 异步同步,这个方案引入了消息队列,实现了业务的解耦合,而且性能较高,吞吐量也比较大,并且支持多数据源的数据同步,不过由于 MQ 是异步消费模型,所以可能出现数据同步延迟的情况,所以实时性要求比较高的场景可能没办法实现。
  3. 基于 Binlog 日志实现,主要原理就是通过监听 MySQL 的 Binlog 日志数据实现增量数据同步,这个方案其基本不会产生代码侵入,而且数据同步的实时性也有一定的保障,不过弊端也比较明显,就是 Binlog 系统的实现可能较为复杂,所以可以考虑使用第三方日志同步组件,最典型的实现就是 Canal 实现。
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
答案说明所有
作者分享
🌟择难路,未有疑,四非学院本运气拉满,春招拿下大厂后端
160
快了,xdm,由于这周上班(小加班),写得有点慢,已经抓紧码字了😝
7
在学校的时候忙毕业的事(主要享受学校时光,有点懒),就每天写一点,发现还不如推倒重写,这今天整理一下😁,先预告一手
11
分析一个真人真事,就是我舍友下午去了一个测试的公司,然后那边说实训2个月,后面打一年工抵工资,我舍友来问我需要注意什么,就我看了一下合同,感觉有点像是招转陪,后面上网查了一下确实是,所以在这里警醒大家,找工作千万要注意,不要因为急给骗了😂
13
晓多科技社招一面 周天体测完全身酸痛,就没去上班,请假在家面试,然后上周投了一个社招1~3 年的岗位,就简单面了一下: 1. 自我介绍 2. 询问背景情况,确认社招,然后校招通过可以提前实习 3. 询问了一下实习情况 4. 直接做一道题,就直接给一个白板,原本要用 IDEA 写,但是我 IDEA 启动不了,直接白板写了,题目要求代码实现,包括数据库表设计和设计模式,我数据库表设计直接用 class 实体类代替了,问题不是很大,然后回答单例完成 ID 生成器包装,策略完成不同类型优惠券计算,然后工厂针对优惠券返回,然后加上责任链进行优惠卷相关参数校验,然后我这里 ID 生成说的时候用雪花算法+基因法冗余了用户 ID 号,然后从 ThreadLocal 里面取出用户 ID对比,避免水平越权,然后代码实现工厂、策略,差不多就这些了 /** * 题目1:电商优惠券系统设计 业务场景: 设计一个电商平台的优惠券系统,需满足: 1.支持多种优惠类型(满减、折扣、赠品)。 2.优惠券可叠加使用,但需校验适用范围(如特定商品/用户等级)。 3.需记录优惠券领取、使用记录,并支持运营按“用户领取量”和“券使用率”分析数据。 重点: 1.数据建模(数据表与关系设计)。 2.面向对象设计(要素+设计模式)。 * * */ 5. 算法题:双向链表反转,直接白板手敲 出去差不多半小时 HR 反馈过了,二面
17
下载 APP