SpringCloud 整合 Canal + RabbitMQ + Redis 实现数据监听!

Canal 介绍

Canal 指的是阿里巴巴开源的数据同步工具,用于数据库的实时增量数据订阅和消费。它可以针对 MySQL、MariaDB、Percona、阿里云 RDS、Gtid 模式下的异构数据同步等情况进行实时增量数据同步。

当前的 canal 支持源端 MySQL 版本包括 5.1.x、5.5.x、5.6.x、5.7.x、8.0.x.

「Canal 是如何同步数据库数据的呢?」

Canal 通过伪装成 MySQL 从服务向主服务拉取数据,所以先来了解一下 MySQL 的主从复制吧。

MySQL 主从复制原理

1、从库(Slave)会生成两个线程,I/O 线程(IOthread),SQL 线程(SQLthread)

2、当 Slave 的 I/O 线程连接到 Master 后,会去请求 Master 的二进制日志(binlog), 此时 Master 会通过 logdump(将主库的二进制日志文件内容传输给从库的过程)给从库传输 binlog。

3、 然后 Slave 将拿到的 binlog 日志依次写入 Relaylog(中继日志)的最末端,同时将读取到的 Master 的 bin-log 的文件名和位置记录到 master-info 文件中,作用为了让 Slave 知道它需要从哪个位置和哪个日志文件开始同步数据,以保证数据的一致性,并且能够及时获取到 Master 的新的更新操作,开始数据同步过程。Slave 不仅在启动时读取 master-info 文件,而且会定期更新该文件中的记 录,以确保记录都是最新的。

4、最后 SQL 线程会读取 Relaylog,并解析为具体操作(比如 DDL 这种),来实现主从库的操作一致,最终实现数据一致。

大致了解完了 MySQL 的主从复制,接着我们看 Canal 就简单啦。

关注微信公众号:【Java陈序员】,获取「开源项目分享、AI副业分享、超200本经典计算机电子书籍等。」

Canal 工作原理

1、Canal Server 与 MySQL 建立连接后,会通过模拟 MySQL Slave 的交互协议,伪装自己为 MySQL Slave,向 MySQL Master 发送 Dump 协议获取数据库的 binlog(二进制日志)文件。

2、Canal Server 解析 binlog 文件,通过网络将解析后的事件传输给 消息中间件(Kafk、RabbitMQ 等),实现数据的实时同步。

了解完 Canal 的原理后,我们就正式开始 RabbitMQ + Canal + Redis 实现缓存和数据库数据一致的功能。

RabbitMQ + Canal + Redis 工作原理

通过上图很好理解:

1、APP 向数据库进行写操作(比如我们更新商品信息啥的)

2、Canal 监听到数据库发生变化,便会向 RabbitMQ 传递数据库发生变化的消息

3、消费者就可以从 RabbitMQ 获取这些消息,然后进行删除缓存操作

下面通过实战让我们更好地理解是如何实现缓存和数据库数据一致性的。

实战配置

Canal 配置

修改 conf/canal.properties 配置

# 指定模式
canal.serverMode = rabbitMQ
# 指定实例,多个实例使用逗号分隔: canal.destinations = example1,example2
canal.destinations = example 

# rabbitmq 服务端 ip
rabbitmq.host = 你的ip(注意不要加端口号哦)
# rabbitmq 虚拟主机 
rabbitmq.virtual.host = / 
# rabbitmq 交换机  
rabbitmq.exchange = canal.exchange  (这是本例子用的交换机)
# rabbitmq 用户名
rabbitmq.username = 你的用户名
# rabbitmq 密码
rabbitmq.password = 你的密码
rabbitmq.deliveryMode =

修改实例配置文件 conf/example/instance.properties

#配置 slaveId,自定义,不等于 mysql 的 server Id 即可
canal.instance.mysql.slaveId=10 

# 数据库地址:配置自己的ip和端口
canal.instance.master.address=你的IP:端口号

# 数据库用户名和密码 
canal.instance.dbUsername=用户名
canal.instance.dbPassword=密码

# 指定库和表
canal.instance.filter.regex=.*\..*    // 这里的 .* 表示 canal.instance.master.address 下面的所有数据库

# mq config
# rabbitmq 的 routing key
canal.mq.topic=canal.routing.key(这是本例子用的key)

然后重启 Canal 服务。

RabbitMQ 配置

这样 RabbitMQ 就配置完啦,下面就是实战代码啦。

实战代码

CanalMessage: Canal 传来的消息

@NoArgsConstructor
@Data
publicclass CanalMessage<T> {
    private String type;
    private String table;
    private List<T> data;
    private String database;
    private Long es;
    private Integer id;
    private Boolean isDdl;
    private List<T> old;
    private List<String> pkNames;
    private String sql;
    private Long ts;
}

RabbitMQ 配置类

@Configuration
@Slf4j
publicclass RabbitConfig {

    /**
     * 消息序列化配置
     */
    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        // SimpleRabbitListenerContainerFactory 是 RabbitMQ 提供的一个实现了 RabbitListenerContainerFactory 接口的简单消息监听器容器工厂。
        // 它的作用是创建和配置 RabbitMQ 消息监听器容器,用于监听和处理消息。
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        //ConnectionFactory 是 RabbitMQ 提供的一个接口,用于创建 RabbitMQ 的连接
        factory.setConnectionFactory(connectionFactory);
        //使用了 Jackson2JsonMessageConverter 将消息转换为 JSON 格式进行序列化和反序列化
        factory.setMessageConverter(  new Jackson2JsonMessageConverter());
        return factory;
    }
}

将消息转换为 JSON 格式,才能映射到 CanalMessage 上。

RabbitMQ + Canal 监听处理类

@Component
@Slf4j
@RequiredArgsConstructor
publicclass CanalListener {

    privatefinal SysMenuService menuService;

    //@RabbitListener(queues = "canal.queue")
    public void handleDataChange(@Payload CanalMessage message) {
        String tableName = message.getTable();

        log.info("Canal 监听 {} 发生变化;明细:{}", tableName, message);
        if (Arrays.asList("sys_menu", "sys_role", "sys_role_menu").contains(tableName)) {
            log.info("======== 清理菜单路由缓存 ========");
            menuService.cleanCache();
        }
    }
}

menuService 的 cleanCache() 是把登录时的路由列表缓存清除掉,

具体可去源码查看,在最底下。

这样我们实现缓存和数据库数据一致性的功能就完成啦,接下来测试一下。

测试

我们直接通过手动修改数据库来完成测试。

我们在菜单表修改菜单管理的内容改成菜单管理 1,点击保存

可以看到更新操作已经被监听到啦,接着就完成清理缓存操作咯,然后就可以防止缓存和数据库数据不一致的问题啦。

### 使用 Canal 和 MQ 实现 Redis 数据同步 #### 1. 架构概述 为了保持缓存和数据库的一致性,可以采用 Canal 结合消息队列(如 RabbitMQ 或 RocketMQ)来捕获 MySQL 的 binlog 变更并将其发送至 Redis。这种方式能够有效减少主库压力,并确保数据最终一致。 #### 2. 安装与配置 Canal 首先,在服务器上部署 Canal 并创建具有适当权限的 MySQL 用户以便 Canal 访问数据库表结构信息以及增量日志文件[^4]。 ```bash sudo systemctl restart mysqld mysql -u root -p CREATE USER `canal`@`localhost` IDENTIFIED WITH mysql_native_password BY 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `canal`@`localhost`; FLUSH PRIVILEGES; ``` 接着按照官方文档指导完成 Canal Server 的安装过程,调整参数以匹配实际环境需求[^3]。 #### 3. 设置消息中间件 选择合适的消息队列组件作为传输通道连接生产端 (Canal Client) 和消费端 (Redis Consumer),这里推荐使用性能优越且社区活跃度高的 Apache RocketMQ 或者易用性强的 RabbitMQ 来承担此角色[^2]。 对于消费者部分,则需编写应用程序监听指定主题/队列中的变更通知并将接收到的数据更新到目标存储系统——即本案例里的 Redis 中去[^1]。 #### 4. 开发 Canal Client 应用程序 基于所选编程语言开发 Canal Client 程序负责订阅来自 Canal Server 发布出来的 DDL/DML 操作记录;随后依据业务逻辑处理这些事件再通过选定的消息协议推送给下游服务节点进行进一步加工保存。 以下是 Python 版本简单示例: ```python from canal.client import SimpleClient import json import pika def callback(ch, method, properties, body): message = json.loads(body.decode()) event_type = message.get('eventType') data = message.get('data') # 将 JSON 消息解析为字典对象 parsed_data = {item['name']: item['value'] for item in data} if event_type == 'INSERT': pass # 插入操作对应的 Redis 更新逻辑 elif event_type == 'UPDATE': pass # 修改操作对应的 Redis 更新逻辑 elif event_type == 'DELETE': pass # 删除操作对应的 Redis 更新逻辑 if __name__ == '__main__': client = SimpleClient() connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() try: while True: entry = client.get_without_ack(100) # 获取最多100条未确认的日志项 entries = list(entry.entries) for e in entries: row_change_list = [] for r in e.rowDataList: column_map = {} for c in r.afterColumnsList or []: column_map[c.name] = str(c.value) row_change_list.append({ "type": e.header.eventType, "table": e.header.tableName, "columns": column_map }) channel.basic_publish( exchange='', routing_key='sync_queue', body=json.dumps(row_change_list), properties=pika.BasicProperties(delivery_mode=2,) # make message persistent ) finally: connection.close() ``` 上述代码片段展示了如何利用 PyMySQL、Pika 库构建一个简易版 Canal Client,它会持续轮询获取最新的 Binlog 日志并通过 AMQP 协议向远程 Broker 推送序列化后的更改详情给后续环节做持久化管理。 #### 5. 测试验证 最后一步是对整个链路进行全面测试,包括但不限于模拟不同类型的 SQL 执行动作观察其能否被及时捕捉转发至目的地址;另外还需关注异常场景下的恢复机制设计,比如网络抖动期间丢失的消息重传策略等。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值