深入Canal解构消息队列单点性能瓶颈

本文讲述了在使用Canal订阅binlog过程中遇到的消息默认发送问题,作者通过探索解决方案、深入源码分析,最终发现并解决分区配置导致的单点性能问题,揭示了DDL操作的特殊行为。
摘要由CSDN通过智能技术生成

1. 业务场景

在使用Canal订阅binlog的过程中,我们观察到一个现象:尽管有大量的Topic,消息却默认发送到MQ的第一个队列。这导致当消费者服务启动并查询动态注册的消费者时,只有最先启动的消费者能够注册大量消费者,进而造成单点性能问题。

2. 解决方案探索

首先想到的是从消费者端进行负载均衡

思路1:

原来的消费者服务作为代理服务,负责转发消息到新的Topic,然后新增一个消费者服务,消费新的Topic

逻辑处理清晰易懂,但是需要新增一个服务

思路2:在原来的消费者服务基础上,进行负载均衡,按照表名进行Hash。不同的服务消费对应的Topic

目测可行,由于下面找到问题的根本原因,所以没试

3. 深入Canal源码

在我开始阅读Canal的源码时,我们发现在初始化canalMQProducer.init时,会构建一个线程池,这个线程池管理着对不同RocketMQ队列的消息发送任务。进一步的研究让我们找到了partitionsNum和partitionHash这两个关键的参数配置在这里插入图片描述
下面贴出我们生产上当时的配置

canal.mq.dynamicTopic=bond_ext.abnormal_price,bond_ext_etl.com_default_course
canal.mq.partition=0
# hash partition config
# canal.mq.partitionsNum=4
# canal.mq.partitionHash=$[PARTITION_HASH]

canal.mq.partition=0,这个参数的意思就是消息只会发送到Topic的0号队列

我当时脑子立刻浮现的想法是:这不就是问题所在嘛,但是我们的业务场景是单个表的变更都在同一个队列即可,而不是固定发送到0号队列,应该根据表名去做Hash

其实到这里,不继续追踪源码,也能猜出来问题在这里,于是我就改配置测试下

# canal.mq.partition=0
canal.mq.partitionsNum=4
canal.mq.partitionHash=etl_test_rocsea.abs_com_relation:id

但是测试结果如下,我的预期是让它随机选一个队列,但是这样单个表的变更发到4个队列,不能做到局部有序了。我直接解释下:这里其实会根据ID的值去Hash
在这里插入图片描述
这时候我脑子灵机一动,那么如果我不设置ID呢?改成这样呢,是不是就会根据表名做hash

# canal.mq.partition=0

canal.mq.partitionsNum=4
canal.mq.partitionHash=etl_test_rocsea.abs_com_relation,etl_test_rocsea.com_default_course

于是我拿了几张表做测试,果然达到我的预期结果,同一个表的变更只会发到一个队列,并且不同的表会随机发到不同的队列,再也不是永远只会发送到第一个队列啦!!!!
在这里插入图片描述
在这里插入图片描述

4. 彩蛋后续(发现坑)

如果你有上述的类似问题,看到这里心情一定非常愉悦了,感觉可以直接开干了,但是当我看完 canal完整的源码,发现里面还是有坑的。这里先贴出我当时看到的关键代码
在这里插入图片描述

这里直接贴出我画的源码流程图,里面包含我对messagePartition方法详细的解剖,对应图中圈出来的红色部分。
在这里插入图片描述

那么我就来测试验证我发现的坑,这个坑就是"如果是ddl就会固定发到0号队列",这里直接贴出我测试的结果,其中1号队列的2026条消息是增删改,而0号队列的2条消息,分别是我操作的TRUNCATE操作以及修改字段名。

0号队列",这里直接贴出我测试的结果,其中1号队列的2026条消息是增删改,而0号队列的2条消息,分别是我操作的TRUNCATE操作以及修改字段名。
在这里插入图片描述

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽量回答您的问题。 首先,您需要在docker中部署canal,可以使用以下docker-compose.yml文件: ``` version: '3' services: canal-server: image: canal/canal-server:v1.1.4 ports: - 11111:11111 environment: - canal.instance.master.address=mysql:3306 - canal.instance.dbUsername=canal - canal.instance.dbPassword=canal - canal.instance.connectionCharset=UTF-8 - canal.instance.tsdb.enable=true - canal.instance.gtidon=false - canal.instance.filter.query.dcl=false - canal.instance.filter.query.ddl=false mysql: image: mysql:8.0.30 ports: - 3306:3306 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=test - MYSQL_USER=canal - MYSQL_PASSWORD=canal ``` 然后,您需要创建一个example配置文件,用于同步mysql到rabbitmq。可以参考以下示例配置: ``` canal.instance.master.address = 127.0.0.1:3306 canal.instance.dbUsername = canal canal.instance.dbPassword = canal canal.instance.filter.regex = .*\\..* canal.instance.filter.black.regex = mysql\\.slave_.* canal.mq.topic = example canal.mq.partition = 0 canal.mq.dynamicTopic = false canal.mq.partitionsNum = 3 canal.mq.groupId = GID_example canal.mq.accessKey = canal.mq.secretKey = canal.mq.namespace = canal.mq.producerGroup = PID_example canal.mq.maxRetryTimes = 3 canal.mq.batchSize = 16384 canal.mq.maxRequestSize = 1048576 canal.mq.sendTimeout = 3000 canal.mq.compressType = none ``` 接下来,您需要在docker中部署mysql 8.0.30,您可以使用以下docker-compose.yml文件: ``` version: '3' services: mysql: image: mysql:8.0.30 ports: - 3306:3306 environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=test - MYSQL_USER=canal - MYSQL_PASSWORD=canal ``` 最后,您需要在docker中部署rabbitmq并创建一个消息队列,可以使用以下docker-compose.yml文件: ``` version: '3' services: rabbitmq: image: rabbitmq:3-management ports: - 5672:5672 - 15672:15672 ``` 然后,您可以使用canal同步mysql到rabbitmq,只需将上面提到的example配置文件中的canal.mq.topic设置为您创建的rabbitmq队列名称即可。 希望能帮到您!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值