做的一个报表项目需要每天定時生成报表并推送,设计的方案是每天凌晨生成分片(分片即所有报表的一部分,全部分片就是报表的全量数据每个分片50个报表中首尾报表的id);计算出来的分片发到rabbitmq
A队列(主角以路人甲的面孔出现),为了分流到下游的各个服务实例(k8s中的pod),每个服务实例再根据分片取出報表进行生成;生成结束再将分片发送到rabbitmq实现的延迟队列B,实现延迟发送刚完成方案时,反复测试一切正常
在某一个luck day, 当把分片调到10个時大奖出现了:报表被重复推送,在延迟队列B中可以看到重复的分片数据第一个怀疑的就是生成分片有问题
,生成分片经过数不清的細节修改之后感觉已经随时可能有问题,但一路排查下来反复查看分片日志没有发现问题,源码看了又看也没有发现问题(太不自信导致浪费了大把时间),又往下游排查:根据分片生成报表从这个流程里的日志可以看到接受到了来着A队列的重复分片,那就是上游:生成分片有问题了?又往后排查怎么都看不出问题,排查了大半天没有一点进展一切都是怀疑,就是拿不出证据(这时候除了吃個苹果喝个水没有任何活动,整个人木在屏幕前);可能是这份真诚感动了上苍突然一个念头闪过,为啥不看看rabbitmq日志说不定有线索,(还好之前有输出spring
amqp的日志文件不然不知道猴年马月才能找到背后凶手);more springamqp.log, 一大堆exception闪闪发光,这是猿生以来第一次感觉exception这么可爱,这┅堆exception中”PublisherCallbackChannel is closed“瞬间抓住了我的眼(这家伙好像之前碰到过,但是后面突然有消失了)
顺着线索从一路摸到了;一边看人家的api guide, 一边擦汗;感觉专门就是针对我的情况发的手册(nb,大牛不光得会写代码还得会预测):
一大堆英文,看的真是头疼没办法,自己挖的坑一边查牛津一边脑海中重演事故现场: rabbtimq channel不是线程安全的,多个线程共享一个channel某些操作会导致并发问题;所以官网的建议是:by using a channel per
thread.(总算有认识的单詞了:每个线程使用一个channel)。回到项目中可以在rabbitmq管理页面看到队列A情况:一个服务实例只有一个channel,而prefetch是250当多个分片消息发送问题就来叻
每个实例能够fetch多达250条消息,但是每个实例只有一个channel一个线程处理一条消息,导致了多个线程共用了这个channel结果就是在线程处理完生成報表,channel.basicAck时出现了PublisherCallbackChannel is closed异常但是在发生异常之前,已经将分片发送到延迟队列B;而这个异常发生后重新创建channel
新的channel重新发送没有成功ack的消息,所以出现了重复的分片消息
client也跟着升级而新版客户端配置覆盖了项目之前的配置(估计删除队列让服务重新声明队列能够恢复之前的配置),为了保险起见将客户端版本调回老版本,并且在@RabbitListener注解中指明concurrency='8',
保证一个线程使用一个信道问题完美解决
但是rabbit配置的是10个线程