不知道你有没有经历过这种时刻:订单状态在后台改了,客户端购物车也清了,但前端搜索里还是旧数据,用户截图投诉,研发查了半天,最后发现是ES里的文档压根没更新。
我遇到这个问题时,第一反应是在业务代码里把更新ES的代码补上。结果补了三个入口,漏了第四个。后来我想明白一件事:靠人肉在各个业务入口补同步代码,这条路走不到头。最终让我脱身的是Canal——阿里巴巴开源的MySQL Binlog增量订阅解析组件,配合它的canal-adapter模块,在Linux服务器上就能把MySQL的数据变更实时推送到Elasticsearch。这篇文章就是整个接入过程的记录:从选型、环境准备、部署配置,到映射设计、验证方式和线上踩坑,尽量把细节说透。
先说结论:这套方案最大的价值是业务代码零侵入。MySQL的Binlog记录了一切数据变更,Canal把自己伪装成MySQL的从库去拉取Binlog,解析成结构化事件后再交给下游,下游不管是ES、消息队列还是另一个数据库,都跟业务代码没有任何关系。也就是说,你不需要在订单服务、用户服务、商品服务里各写一遍“更新ES”的逻辑,只要数据库里发生了变更,同步链路会自动感知。
1. 为什么我放弃了“双写”和“定时任务”,选了Canal这条链路
1.1 双写和定时任务,问题的本质出在哪
最早团队讨论同步方案时,有人提议在业务代码里双写:更新MySQL的同时,同步调ES接口写入。这方案听起来直接,但真正落地后很痛苦。第一次碰到的问题是事务边界不一致:MySQL更新成功、ES写入失败,你怎么办?重试?记录日志人工补偿?每个业务入口都要单独处理这些异常分支,代码侵入性极强。更麻烦的是,一旦产品经理说“历史数据也要进ES”,双写方案就彻底失效了,因为你不可能为了历史数据把代码重新跑一遍。
后来退而求其次,用定时任务做全量扫描同步。每晚扫一次MySQL,把变更数据刷进ES。这方案维护成本低,但时效性很差。用户修改了昵称,搜索里第二天才更新,这在很多场景下根本没法接受。而且全表扫描对数据库的压力很大,一张千万级别的表,稍微没控制好频率,慢查询日志马上刷屏。最要命的是“删除”操作,如果业务做的是物理删除,定时任务扫描时这条数据已经没了,ES里那条脏数据永远不会被发现,除非再做额外的对账任务。
所以问题的本质是:业务数据变更的源头在MySQL Binlog里,而双写和定时任务都没法干净地拿到这个源头事件。双写是“预测未来需要哪些事件”,注定预测不全;定时任务是“事后补救”,注定有延迟和漏洞。
1.2 Canal是怎么工作的:伪装成MySQL从库
Canal的核心原理其实不复杂。MySQL主从复制大家应该都了解:主库把变更写进Binlog,从库开启一个IO线程去主库拉取Binlog,写入自己的Relay Log,再由SQL线程回放。Canal就是山寨了这个过程——它把自己伪装成一个从库,向MySQL发送dump协议请求,MySQL以为来了一个正常的同步从库,就把Binlog推给Canal。Canal拿到Binlog后解析成结构化的事件,比如Insert、Update、Delete,再交给下游。
这条数据流是:
text复制MySQL Binlog → Canal Server → Canal Adapter → Elasticsearch
Canal Server负责跟MySQL打交道,解析Binlog;Canal Adapter负责把解析出来的数据变成ES能接受的请求。如果不想用Adapter,也可以自己写一个Canal Client程序,监听在Canal Server上,自己拼ES请求。但大多数场景下,Adapter已经够用了。
这里有个很重要的点:Canal只认Binlog,不关心业务逻辑。你在哪个库表上做了DML,它就能感知到。这决定了它不仅能解决增量同步,还能在接入后立刻把存量数据通过Adapter的全量同步接口灌进ES,实现“存量+增量”的无缝衔接。
1.3 为什么最后选了Canal而不是DTS或自研采集器
如果你用的是云数据库,云厂商一般会提供DTS之类的数据传输服务,也能做MySQL到ES的同步。我评估过,DTS的优点是托管、免运维、控制台点点就能配置,但它有几个让我不舒服的限制:一是同步配置的可视化程度低,复杂字段映射不好调;二是它是个黑盒,出了问题你只能提工单;三是在我们这种混合部署的IDC环境里,DTS经常没法直连ECS上的自建MySQL。Canal则完全可控,配置项都在本地文件里,出了问题还能看日志定位,虽然需要自己维护,但胜在透明。
自研采集器我也想过,但很快就否决了:解析Binlog的协议栈非常复杂,要处理MySQL各种版本差异、字段类型映射、位点管理、断线续传,这已经是一个团队的活。社区有Canal这种成熟方案,没有必要重复造轮子。
2. 环境准备阶段最容易翻车的几个细节
2.1 版本匹配:JDK、MySQL、ES、Canal哪个都不能拍脑袋
很多人装Canal第一步就卡在版本上,不是因为不会敲命令,而是因为各组件版本互相不兼容。我先说一下我用的一套稳定组合,然后再讲为什么是这个组合:
| 组件 | 版本 | 备注 |
|---|---|---|
| MySQL | 5.7.x / 8.0.x | 必须开启Binlog且格式为ROW |
| Canal Server | 1.1.7 | 阿里云开源版本 |
| Canal Adapter | 1.1.7 | 与Server同版本配套 |
| Elasticsearch | 7.10.2 | Adapter的es7适配器对应ES 7.x |
| JDK | 1.8 | Canal官方推荐,高版本JDK偶发兼容问题 |
Canal 1.1.7支持MySQL 5.7和8.0,但MySQL 8.0的默认认证插件是caching_sha2_password,Canal连接时可能报认证失败。解决办法是创建同步账号时指定mysql_native_password,具体命令后面会说。如果你用MySQL 8.0.30以上的版本,可能还需要在Canal Server所在服务器加一行JVM参数跳过某些认证逻辑,这个我在第三部分会提到。
Elasticsearch这边要注意,Canal Adapter 1.1.7的es6和es7是两个不同的适配器。如果你用的是ES 8.x,需要去Canal项目发行版里找对应的扩展包,或者改用Canal 1.1.8+以上的版本。当初我在ES 8.x上折腾过一次,默认的es7适配器连不上8.x的HTTPS接口。所以如果你还没装ES,建议直接用ES 7.10这种长期稳定版本;如果已经装了ES 8,那就先去确认Canal版本对ES 8的支持情况再动手。
2.2 开启Binlog与创建同步账号:两条命令背后的原理
MySQL默认不一定开了Binlog,而且默认格式可能不是ROW。Canal要解析出完整的行级变更,Binlog格式必须是ROW,因为只有ROW格式记录的是“哪一行在哪个字段变成了什么值”,而STATEMENT格式记录的是SQL语句本身,对同步来说信息不够用。
修改/etc/my.cnf:
ini复制[mysqld]
log-bin=mysql-bin
binlog_format=ROW
server_id=1
注意server_id不能和已有从库冲突。改完重启MySQL:
bash复制systemctl restart mysqld
然后验证是否生效:
sql复制SHOW VARIABLES LIKE 'log_bin%';
SHOW VARIABLES LIKE 'binlog_format';
如果binlog_format还是STATEMENT,说明my.cnf可能没加载到[mysqld]段,或者文件位置不对。用mysqld --verbose --help | grep -A 1 'my.cnf'查一下读取顺序。
创建Canal专用账号:
sql复制CREATE USER 'canal'@'%' IDENTIFIED WITH mysql_native_password BY 'canal_pass';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;
这三个权限不是随便给的。SELECT权限是让Canal在解析Binlog时能读取表结构做字段映射;REPLICATION SLAVE和REPLICATION CLIENT是MySQL从库协议要求的,Canal要作为“伪从库”去拉Binlog,必须有这两个权限。我见过有人图省事直接给了ALL PRIVILEGES,虽然也能跑,但安全上不建议。另外,如果你用的是MySQL 8.0,记得加上WITH mysql_native_password,不然Canal会卡在认证上。
2.3 Linux侧的基础环境:磁盘、内存和日志目录
在真正部署前,我建议先看一眼服务器的磁盘剩余空间。Canal和ES的日志都会膨胀,尤其是ES的索引数据,一个TB级的索引目录非常常见。我给Canal Server单独划分了/opt/canal目录,给ES单独挂载了数据盘,避免日志写满系统盘。
内存方面,Canal Server默认JVM堆内存是1GB左右,ES的话至少给它4GB以上,这个根据数据量灵活调整。如果你只有一台2核4G的机器要同时跑MySQL、ES和Canal,那会比较吃力,建议至少把Canal和ES拆到两台机器,或者给机器升配。
3. Canal的部署和配置:deployer和adapter到底各自干什么
3.1 Canal Server(deployer)的安装和instance配置
Canal Server在官方发行包里叫canal.deployer-1.1.7.tar.gz。解压到/opt/canal后,目录结构大概是这样的:
text复制/opt/canal
├── bin
│ ├── startup.sh
│ └── stop.sh
├── conf
│ ├── canal.properties
│ └── example
│ └── instance.properties
└── logs
canal.properties是Server级别的配置,instance.properties是单个数据源实例的配置。一个Canal Server可以同时跑多个instance,每个instance连接一个MySQL实例。单机场景下保持默认的canal.destinations=example就行。
重点是conf/example/instance.properties:
properties复制# MySQL地址
canal.instance.master.address=127.0.0.1:3306
# 同步账号
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal_pass
# 连接字符集
canal.instance.connectionCharset=UTF-8
# 库表过滤规则
canal.instance.filter.regex=test_db\\..*
canal.instance.filter.regex这块坑了不少人。它的规则是库名\\.表名,而且配置文件里反斜杠本身也是转义符,所以写test_db\\.user才表示匹配test_db.user这张表。默认的.*\\..*是匹配所有库所有表,生产环境一定要改成自己要同步的库,不然Canal会把所有库的Binlog事件都拉下来,白占内存和带宽。
启动Canal Server:
bash复制/opt/canal/bin/startup.sh
然后看日志:
bash复制tail -f /opt/canal/logs/example/example.log
如果看到类似dump address 127.0.0.1:3306 has been successfully的日志,说明Canal已经和MySQL握手成功,开始拉Binlog了。
3.2 Canal Adapter的安装和连接配置
Canal Adapter的发行包是canal.adapter-1.1.7.tar.gz,解压到/opt/canal-adapter。它才是真正跟ES打交道的组件。
它的入口配置是conf/application.yml,核心片段如下:
yaml复制canal.conf:
mode: tcp
canal.server.host: 127.0.0.1:11111
srcDataSources:
defaultDS:
url: jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: canal
password: canal_pass
canalAdapters:
groups:
- groupId: g1
outerAdapters:
- name: es7
hosts: http://127.0.0.1:9200
mode: tcp表示Adapter通过TCP直连Canal Server。如果生产环境有多个消费者,建议把Canal Server的消息投递到Kafka或RocketMQ,然后Adapter以kafka模式消费。单机小流量用TCP最简单,踩坑也少。
srcDataSources这个配置很容易被忽略。Adapter在同步时,除了接收Canal推送的Binlog事件,还需要根据SQL定义回查MySQL库里的原始数据,所以必须配置一个数据源连接。这个账号可以和Canal的同步账号是同一个,但需要额外保证有对应库表的读写权限。
outerAdapters里name: es7意味着用的是ES 7.x适配器。如果ES是6.x,这里要写es6。这个字段别想当然,我之前就见过有人ES 6.x却写es7,同步时报了一堆版本不兼容的错。
启动Adapter:
bash复制/opt/canal-adapter/bin/startup.sh
然后看日志:
bash复制tail -f /opt/canal-adapter/logs/adapter/adapter.log
正常启动时日志里会打印canal adapter started,并且能看到它注册的同步规则列表。
4. 从Binlog到ES文档:映射配置里的关键细节
4.1 索引映射与主键策略
Adapter同步到ES的规则,是放在conf/es/目录下的YAML文件里。每个YAML文件对应一套同步规则,比如我要同步test_db库的user表,就新建一个user.yml:
yaml复制dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
_index: user_index
_id: id
sql: "SELECT id, name, age, create_time FROM user"
commitBatch: 3000
dataSourceKey对应application.yml里配的数据源名称;destination对应Canal Server的instance名称;_index是ES里的索引名;_id是ES文档ID,这里我用的是MySQL主键id。
注意_id的选择非常关键。ES文档ID就是文档的唯一标识,如果MySQL表里没有天然唯一字段,你需要自己拼一个。比如订单表用order_id,日志表用自增id就行。但如果你同步的是多表JOIN的结果,比如订单表和用户表联查,_id就可能是order_id的别名。曾经有个项目,同步时用了MySQL的自增id但业务逻辑里删除重建数据,导致ES里出现了大量文档ID冲突,更新互相覆盖,那类问题排查起来非常痛苦。
sql字段支持别名,也可以做简单的字段变换。比如MySQL字段是create_time,ES里想叫createdAt,可以直接SELECT create_time AS createdAt FROM user。Adapter会用这个SQL去源库查数据并做字段名映射。
4.2 字段类型陷阱:从MySQL到ES的类型转换
ES的字段类型和MySQL差异很大,Adapter不会自动做完美的类型推导,很多时候需要我们提前在ES里建好索引mapping,否则ES会自动推断字段类型,后面查询聚合时才发现不对。
常见的对应关系:
| MySQL类型 | ES推荐类型 | 说明 |
|---|---|---|
| int/bigint | integer/long | bigint对应long,int+5这种表达式别在ES里做 |
| varchar | keyword或text | 精确匹配、聚合用keyword,全文搜索用text |
| datetime/timestamp | date | 需要指定format或统一转时间戳 |
| decimal | scaled_float或keyword | 金额建议scaled_float,双精度double会丢精度 |
| json | 不推荐直接用嵌套对象 | JSON建议映射为keyword或拆成子字段 |
这里特别说下时间字段。ES的date类型默认解析ISO8601格式,但MySQL的datetime是2024-01-01 12:00:00这种格式,如果不指定format,Adapter同步过去之后ES可能解析失败,或者文档索引报错。稳妥做法是先在ES侧建好mapping:
json复制{
"mappings": {
"properties": {
"create_time": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
还有一种做法是在sql里用UNIX_TIMESTAMP(create_time) * 1000 AS create_time转成毫秒时间戳,ES侧用epoch_millis解析。我比较推荐这种,因为时间戳不涉及时区解析问题,而且排序、范围查询都很方便。
建议:在启动Canal同步之前,先用
curl -X PUT把目标索引的mapping建好。等Canal开始灌数据之后再发现类型不对,重建索引的成本会高很多。
4.3 手动建索引启动同步
索引建好后,重启一下Adapter,让它加载新的user.yml规则:
bash复制/opt/canal-adapter/bin/stop.sh
/opt/canal-adapter/bin/startup.sh
然后去MySQL里执行一条INSERT,看Adapter日志:
text复制sync ... success, cost: 12ms
再用curl确认ES里已经有这条数据:
bash复制curl -X GET "http://127.0.0.1:9200/user_index/_search?pretty" -H "Content-Type: application/json" -d '{"query":{"match_all":{}}}'
如果ES里出现了刚才INSERT的数据,说明基础链路已经通了,后面就是做存量同步和压测调优。
5. 数据校验、断点续传与性能参数调整
5.1 如何确认同步真的在跑:从日志到对账脚本
很多人装上Canal之后,插入一条数据发现ES里有了,就以为万事大吉。其实增量同步的验证要比这严格得多。
我自己的流程分三步:
第一步,看日志。Adapter日志里如果持续打印sync ... success,说明它一直在消费Binlog事件。如果长时间没有日志输出,也不一定代表坏了,可能只是没有数据变更。这时候你需要自己制造几条变更来验证。
第二步,做DELETE和UPDATE的验证。很多人在验证阶段只测了INSERT,没测UPDATE和DELETE,结果上线后发现更新操作不同步、删除操作不同步。Adapter对三种操作的处理路径是不一样的,尤其是删除操作,如果_id映射不对,可能根本删不掉ES里的文档。
第三步,定期对账。写一个简单的定时脚本,比如每小时对比MySQL行数和ES文档数,或者比最近一条更新记录的时间。脚本不用很复杂,能发现明显不一致就够了:
bash复制mysql -h127.0.0.1 -ucanal -pcanal_pass -e "SELECT COUNT(*) FROM test_db.user" > /tmp/mysql_count.txt
curl -s -X GET "http://127.0.0.1:9200/user_index/_count" > /tmp/es_count.json
然后比对数值。如果差异过大,就重点排查。
5.2 断点续传:重启不丢数据的关键
Canal支持的断点记录有两种方式:一种是用ZooKeeper记录位点,适合多节点或需要高可用的环境;另一种是本地文件记录,也就是meta.dat。单机场景默认就用本地文件。
位点文件路径是/opt/canal/conf/example/meta.dat,文件里面会记录当前已经消费到哪个Binlog文件名和偏移量。Canal重启后会从上次记录的位置继续拉Binlog,这就是断点续传的核心。
这里提醒一句:千万注意别随便删meta.dat。如果你为了“重置同步”删了这个文件,Canal会从当前最新的Binlog位置开始消费,这中间的所有增量变更都会丢失,ES里就是一段数据空白。我之前在一台测试机上删过一次,结果造成的后果是ES里少了将近一天的增量数据,最后只能全量重灌。
如果你确实需要从头同步,正确做法是:先跑Adapter的全量同步接口,把存量数据灌进ES,再保留Canal的位点继续消费增量。全量同步的接口一般是:
bash复制curl -X POST "http://127.0.0.1:8081/sync?table=user"
这需要Adapter的http端口是开着的。这种先全量后增量的流程,比删meta.dat安全得多。
5.3 性能参数调整:从batch size到ES写入压测
Canal的调优参数主要有几个维度:Canal Server端拉取Binlog的批次大小、内存队列容量、Adapter端批量写入ES的条数。
Canal Server端,canal.properties里常见调整:
properties复制canal.instance.batch.size=2048
canal.instance.memory.buffer.size=32768
canal.instance.memory.buffer.memunit=1024
batch.size是每次拉取Binlog事件的最大条数;memory.buffer.size是内存队列长度,单位是事件条数。同步数据量很大的话,可以适当调大这两个值,但注意内存占用也会随之上升。
Adapter端,user.yml里的commitBatch控制每批写入ES的文档数量。默认3000条一批。这个值太小,写入吞吐上不去;太大,一次性占用ES的bulk内存可能过大,反而拖慢ES。我实测下来,单表千万级数据时,commitBatch设为3000到5000之间,ES写入吞吐表现比较稳定。
批量写入压测时,我推荐先用curl直接压ES的_bulk接口,确定当前ES集群能承受的最大吞吐,再反推Canal这边的batch设置。不然上来就调大所有参数,ES很容易被打到红色健康状态。
6. 线上踩坑实录:几个日志不会直接告诉你的问题
6.1 时区问题:ES里的时间怎么差了8小时
这是同步后最容易发现的问题:MySQL里明明存的2024-01-01 12:00:00,到ES里查出来变成2024-01-01 04:00:00或者尾部带上了+08:00。
原因很简单,ES内部默认使用UTC存储date类型,查询时再根据Kibana或客户端的时区设置显示。Canal Adapter从MySQL读出来的时间字符串本身不带时区,ES就按UTC解析了。差8小时就是因为北京时间比UTC快8小时。
解决办法有两个方向。一个是在Adapter的数据源连接串上加serverTimezone=Asia/Shanghai,让驱动在读取MySQL时间时按指定时区处理;另一个更省心的办法是我前面提到的,在SQL里把时间字段转成毫秒时间戳。用时间戳配合ES的epoch_millis格式,完全绕开了时区解析的歧义,这也是我在多个项目里验证过的稳妥方案。
6.2 Adapter启动报错:版本不兼容和lib目录缺失
Adapter启动时报NoClassDefFoundError,或者提示找不到ElasticsearchClient相关类,基本可以判断是版本适配问题。
Canal Adapter的es7适配器依赖于ES官方客户端库。如果你下载的是基础版发行包,有些依赖包并不会自动带全。打开/opt/canal-adapter/lib目录,看看里面有没有elasticsearch-rest-client、elasticsearch-rest-high-level-client这些依赖包。如果没有,需要去Canal项目的发行说明里找对应版本的插件包手动补齐。
还有一种情况是ES开启了HTTPS和认证,Adapter的application.yml里还只写了http://127.0.0.1:9200,连接时直接报证书或认证错误。需要在outerAdapters下面增加证书相关配置,或者先在ES侧关掉安全认证把链路跑通,再加固。
6.3 DDL变更和字段类型变化:Adapter最怕的事
如果某天业务方在原表上加了一列,或者把一个varchar字段改成了text,Canal Server通常不会报错,但Adapter在回查SQL时可能就查不到新列,或者ES那边字段类型冲突,这条数据同步失败。
我在生产上遇到过一次:运营在后台手工给某张表加了一个备注字段,结果那天的增量数据全部没同步到ES。查日志发现Adapter一直报字段映射失败,因为user.yml里的SQL写的是SELECT id, name, age FROM user,压根没查这个新字段,但因为Binlog事件里带出了新字段的变更,Adapter尝试用旧SQL回查时就直接挂了。
从那以后,我养成了一个习惯:每次业务表结构变更,不管加列还是删列,都同步检查Canal Adapter的映射文件,并平滑重启Adapter。如果只是加列,通常只需要改sql字段,不需要重建索引,ES写入时也会自动把新字段加进去。
6.4 MySQL连接空闲断开和Canal假死
Canal Server启动后长期不产生增量数据,MySQL连接有可能被服务端的wait_timeout杀掉了。之后一旦有数据变更,Canal可能报连接异常,需要重启才能恢复。
这个问题的排查特征是:Adapter日志没有明显错误,但数据就是不更新。后来我在instance.properties里增加了连接超时控制,并确认MySQL侧的wait_timeout设置不影响Canal,同时建议在监控层面加上Canal的存活检查,进程在但可能已经假死的情况非常坑人。
一个简单的守护方式是crontab定期检查进程和端口:
bash复制*/5 * * * * /usr/bin/curl -s http://127.0.0.1:11111 > /dev/null || /opt/canal/bin/startup.sh
如果端口不通就拉起进程。虽然粗暴,但至少比用户发现数据不同步时再人工干预强。
6.5 多表JOIN同步的坑
Adapter的SQL支持JOIN,比如订单表和订单明细表联查后,一个订单文档里嵌套明细信息。这种模式能做,但要注意一个关键问题:当只有订单明细表的数据发生变化时,Canal只会推送明细表的变更事件,Adapter用JOIN SQL回查时,虽然能查出最新结果,但如果你在旧版本的Adapter里没配置好触发关系,主文档可能不会更新。
这个问题在Canal Adapter的文档里提过,大致意思是:JOIN同步场景下,如果子表更新需要联动主表文档更新,需要确保配置里的关联关系成立,否则可能会出现明细变了、ES里主文档没变的情况。
所以我的建议是:简单的单表同步用Canal Adapter就够了,复杂JOIN或者宽表实时拼接,最好在同步链路上加一层消息队列,让下游自己消费数据做多表合并。不要让Adapter去做过于复杂的JOIN,那是把复杂逻辑塞给了一个不太擅长处理它的组件。
6.6 从ES侧验证数据质量
最后一个坑其实不在Canal,而在ES本身。ES为了搜索性能,索引的mapping一旦建立,字段类型就不能随意改。同步前如果没建好mapping,ES自动推断出来的类型可能跟业务预期完全不同。
比如MySQL的varchar,ES会自动映射成text,而且还会带一个.keyword子字段。你在Kibana里看到数据能查,但做terms聚合时发现聚合出来的是分词后的结果,完全不是你想要的。这种问题只能靠重建索引解决。
所以上线前一定要重复自查一遍:目标索引有没有显式mapping?时间字段格式是否一致?是否需要对中文用户名字段做分词?这些确认好了,再让Canal开始灌数据,至少能少一半返工。
我个人的经验是,先花半天把ES索引mapping设计好,再花半天把Canal链路部署起来,整个过程其实可以控制在一个工作日内。真正耗时的是上线后长期运行时各种边界情况的处理,比如表结构变更、时区、驱动版本、网络闪断、磁盘写满。但一旦链路稳定了,维护成本其实很低——不用再改业务代码,不用再补定时任务,MySQL的Binlog会忠实地把每一次变更告诉ES。希望这篇记录能帮你少走一些弯路,尤其那几个日志里看不到的坑,真的踩过才知道有多费时间。
