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
  • 索引字段:idtitlesubTitlesummaryembedding(向量)
  • 使用 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);
    }
}

五、总结

这种架构的好处

  1. 解耦:业务系统不需要直接操作 ES,只管写数据库
  2. 异步:用户请求响应快,ES 更新在后台完成
  3. 可靠:Redis 做消息备份,超时自动补发
  4. 可扩展:可以轻松添加更多消费者处理其他表的变更

数据流一句话总结

用户操作 → MySQL 写入 → Canal 监听 binlog → RabbitMQ 转发 → 消费者消费 → ES 索引更新

posted @ 2026-08-27 18:39  霜华序  阅读(5)  评论(0)    收藏  举报