一次改造实战:版本对齐、编译报错、语义变化和留痕
搜索项目要用 Canal 把 MySQL 的变更增量同步到 Elasticsearch。问题来了:下载的 client-adapter(客户端适配器)默认只支持到 ES 6.4,而项目里用的是 ES 7.3。为了版本统一,只能对 client-adapter 动手改造。
这类改造很容易被写成“改三处版本号,再加个.value,编译通过就完事”。我的观点是:编译通过,只说明语法对了,不说明语义对了。下面是我认为改造时必须想清楚的六件事。
01先确认有没有官方支持,再决定要不要“魔改”
动源码是最后一招,不是第一步。动手前先查一下:你用的 Canal 版本,或者更新的版本,是否已经提供了 ES 7 对应的适配器模块?较新的 Canal 版本在 client-adapter 下拆分了不同 ES 版本的模块(具体模块名和起始版本以官方 release 说明为准,我这里没有逐版本核对)。
取如果官方已支持,直接升级 Canal 版本,省掉自己维护一份分叉代码的长期成本。
舍因为 Canal 其他版本不兼容现有环境、或需要改动其他逻辑,必须停留在当前版本时,才走改造路线,并清楚自己要维护一个补丁。
02改版本号只是开始:先找到该改的三处
在 canal-master 中打开 client-adapter 模块,等 Maven 把依赖更新好,然后调整 elasticsearch 模块 pom 文件里与 ES 版本相关的几处旧版本号(原文是三处,一般是 ES 本体及相关客户端依赖),统一改成与集群一致的 7.3.x。版本号要和集群对齐,客户端版本与服务端同主版本最稳妥。
03打包前先过一关:你用的是 JDK 还是 JRE?
在 canal-master 根目录重新打包:
mvn clean package -DskipTests如果报No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?,意思是 Maven 当前用的是 JRE,没有编译器。原文的做法是在mvn.cmd开头加一行set JAVA_HOME=...,能用,但它修改的是 Maven 的启动脚本,会影响这台机器上所有项目。
更干净的做法把JAVA_HOME设成 JDK 的目录(不是 JRE),重开命令行后用mvn -v确认输出的 Java home 指向 JDK。如果只想临时生效,在当前命令行窗口执行set JAVA_HOME=你的JDK路径后再运行 mvn 即可,不用改mvn.cmd。
另外,这里用-DskipTests是因为我们在构建第三方源码,不是自己的业务项目,可以接受。你自己的项目里不建议养成这个习惯。
04编译报错是在帮你找“破坏性变更”
升级后编译会报错,典型的一条:ESAdapter.java中,TotalHits无法转换为long。原因是 ES 7 把getTotalHits()的返回值,从long改成了TotalHits对象,它里面除了命中数value,还有一个relation。所以要在后面补上.value:
// ES 6:直接返回 long long total = response.getHits().getTotalHits(); // ES 7:返回对象,取命中数要用 .value long total = response.getHits().getTotalHits().value;改完后继续编译。不用从头再来,Maven 会在失败时给出续跑提示,可以用-rf(resume from)从失败的模块继续:
mvn package -DskipTests -rf :失败模块的artifactId编译完成后,直接使用打好的 client-adapter 即可。
05.value 不是终点:语义变化比语法更危险
这是我最想强调的一点。.value让代码能编译,但 ES 6 到 7 有几处语义变化,编译器不会告诉你:
| 变化 | 风险 | 检查方法 |
|---|---|---|
| 总命中数默认有上限 | ES 7 默认只精确计数到 1 万,relation可能是“大于等于”。如果适配器逻辑用总数做判断(比如校验、分页),数据超过 1 万就会出错 | 看.value用在哪;需要精确总数时,查询里设置trackTotalHits |
| Type 被弱化 | ES 7 起 mapping type 已被弃用,统一用_doc。适配器配置里的_type、索引请求里的 type,可能产生警告甚至失败 | 检查同步配置文件里的_type,以及创建索引、写入文档的代码 |
| 客户端类型与端口 | 旧版适配器可能用的是 TransportClient(走 9300 端口),它在 7.x 已被弃用,在 8.0 被移除;REST 客户端走 9200 | 确认适配器实际使用哪种客户端,以及配置里的地址和端口是否对得上 |
判断编译通过后,不要立刻宣布完成。真正的验收是跑一遍增量同步,而不是看构建成功。
编译提示:ESAdapter.java:[225,56] 不兼容的类型,无法转换为long
[ERROR] /canal-master/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java:[225,56] 不兼容的类型: or g.apache.lucene.search.TotalHits无法转换为long
解决方法:在getTotalHits后增加value,因为ES7后扩大了TotalHits的范围
06魔改第三方,要有纪律:留痕、可回放、有验收
- **Fork 并留痕:**把改动放进你自己的 Git 仓库或补丁文件,记录基于哪个版本、改了哪几处、为什么改。下载的源码目录改完就丢,半年后没人知道这个包是怎么来的。
- **固定产物版本:**给打出来的包标上版本和构建日期,在文档里写明它与官方包的差异。
- **做回归验收:**在测试环境对 MySQL 做一次新增、修改、删除,确认 ES 里的文档同步正确;再测试一次超过 1 万条数据的场景,检查总数逻辑;最后看 Canal 和 adapter 的日志里有没有 type 或版本相关的警告。
- **评估长期成本:**以后 Canal 或 ES 再升级,你的补丁要不要重新合并,是否有精力持续维护。
一句话总结:升级版本,改的不只是版本号。编译通过只是起点,语义对齐才是终点:先看官方是否已支持,改版本号后用编译报错找 API 变化,再检查计数上限、type、客户端这些编译器看不到的变化,最后用增量同步测试验收,并把补丁留痕。
说明:版本号、模块名和参数以你使用的 Canal 与 Elasticsearch 实际版本为准。