RabbitMQ + Canal 实现 ES 异步更新机制
一、问题引入
我的数据库随着用户的写入发生了改变,此时如果有用户进行了搜索,那么最新写入的内容并不会被展示出来,那有没有什么工具可以监测数据库然后把这个更改的信息传给ES呢?
有的兄弟有的,可以使用 Canal + RabbitMQ + Elasticsearch去实现数据库变更到搜索引擎的异步同步。简单来说就是:
MySQL 数据变了 → Canal 捕获变更 → RabbitMQ 传递消息 → ES 异步更新
二、各组件职责
1. Canal —— "信使"
作用:监听 MySQL 数据库的变更日志(binlog),当数据库有变化时自动捕获并通知应用层。
项目中的实现:
- Canal 服务独立部署在
192.168.150.131:11111 - 项目通过
travel-modules-canal模块连接 Canal 服务 StrategyListener监听ta_strategy表的变更:
// StrategyListener.java
@Component
@CanalTable("ta_strategy") // 指定监听哪张表
public class StrategyListener implements EntryHandler<StrategyCanal> {
@Override
public void insert(StrategyCanal strategyCanal) {
// 1. 先存 Redis(做消息备份)
String key = RedisKeys.STRATEGY_RABBIT_MESSAGE.getPrefix();
redisService.addCacheZset(key, message, System.currentTimeMillis());
// 2. 再发 RabbitMQ
amqpTemplate.convertAndSend(
RabbitConfig.EXCHANGE_NAME,
RabbitConfig.ROUTING_KEY,
message
);
}
}
Canal 就像一个"快递员",盯着 MySQL 这栋大楼的每一扇门(数据表),一旦有人进出(增删改),它就把消息送到 RabbitMQ 这个"中转站"。
2. RabbitMQ —— "中转站"
作用:作为消息队列,负责在 Canal 和 ES 之间传递消息,实现解耦。
项目中的配置:
// RabbitConfig.java
public static final String QUEUE_NAME = "travel_queue"; // 队列名
public static final String EXCHANGE_NAME = "travel_exchange"; // 交换机名(直连模式)
public static final String ROUTING_KEY = "travel_routingKey"; // 路由键
// 绑定关系:交换机 → 路由键 → 队列
消息流向:
Canal 监听器 → travel_exchange(交换机)
↓
travel_routingKey(路由键)
↓
travel_queue(队列)
↓
TravelRabbitListener(消费者)
3. Elasticsearch —— "搜索引擎"
作用:存储和索引数据,提供快速的全文检索能力。
项目中的索引:
- 索引名:
strategy - 索引字段:
id、title、subTitle、summary、embedding(向量) - 使用
ik_max_word分词器支持中文搜索
三、完整流程(图解)
┌─────────────┐ binlog变更 ┌─────────────┐ 发送消息 ┌─────────────┐
│ MySQL │ ──────────────→ │ Canal │ ───────────→ │ RabbitMQ │
│ (ta_strategy)│ │ (监听器) │ │ (中转站) │
└─────────────┘ └─────────────┘ └─────────────┘
↓
↓ 消费消息
↓
┌─────────────┐
│ Redis │ ← 消息备份/补发
└─────────────┘
↓
↓
┌─────────────┐
│ ES │
│ (搜索引擎) │
└─────────────┘
流程详解:
第一步:MySQL 变更
当业务代码执行 INSERT INTO ta_strategy ... 时,MySQL 会记录 binlog。
第二步:Canal 捕获
Canal 客户端实时监听 binlog,发现 ta_strategy 表有新数据插入。
第三步:发送到 RabbitMQ
StrategyListener.insert() 被触发:
- 将数据序列化为 JSON
- 先存到 Redis ZSet(作为消息备份,防止消息丢失)
- 通过 RabbitMQ 发送消息
第四步:消费消息更新 ES
TravelRabbitListener.receive() 监听到消息:
- 解析 JSON 为 Strategy 对象
- 转换为 StrategyEs 对象
- 调用
ElasticsearchClient.index()更新 ES 索引 - 从 Redis 中删除已处理的消息
四、消息可靠性保证机制
为什么需要 Redis?
RabbitMQ 可能会丢消息,所以项目用 Redis 做了一个"消息保险箱":
// 1. 发送前先存 Redis
redisService.addCacheZset("strategy_rabbit_message", message, System.currentTimeMillis());
// 2. 消费成功后删除
redisService.deleteCacheZsetValue("strategy_rabbit_message", message);
定时补偿任务
StrategyServiceImpl.checkRabbitMessage() 方法会定期检查 Redis 中的"僵尸消息":
public void checkRabbitMessage() {
// 查找超过1分钟未处理的消息
double time = System.currentTimeMillis() - 1 * 60 * 1000;
Set<String> set = redisService.rangeByScore(key, 0, time);
// 重新发送
for (String value : set) {
amqpTemplate.convertAndSend(EXCHANGE_NAME, ROUTING_KEY, value);
}
}
五、总结
这种架构的好处
- 解耦:业务系统不需要直接操作 ES,只管写数据库
- 异步:用户请求响应快,ES 更新在后台完成
- 可靠:Redis 做消息备份,超时自动补发
- 可扩展:可以轻松添加更多消费者处理其他表的变更
数据流一句话总结
用户操作 → MySQL 写入 → Canal 监听 binlog → RabbitMQ 转发 → 消费者消费 → ES 索引更新

浙公网安备 33010602011771号