一,漫谈消息队列

众所周知,消息队列主要是用于消峰,解藕。同时也使得业务的处理方式为异步处理。常用的消息队列有rabbitmq,activemq,rocketmq,kafka等等,考虑到Kafka已经集成到了spring,因此在Java作为主要开发语言的场景,kafka的使用是最为常见的。在消息队列中通常有两种角色:生产者与消费者。而联系两者之间的枢纽则称为队列。考虑到多个队列的存在,每个队列通常都有一个ID进行区分。

二,kafka的基本概念与相当重要的offset

topic:在上一节说明每个队列有个ID,那么这个就是kafka队列的ID。

partition:分区是针对生产者而言,每个topic可以有多个分区,生产者发送消息分别发送到不同的分区。

group:分组是针对消费者而言,每个topic可以有多个分组,kafka能保证相同的topic和分区只能一个分组中同一个消费者消费,因此需要重复消费就必须另一个分组。

at most onece模式:消息最多消费一次。

at least onece模式:消息最少消费一次。

exactly onece模式:消息恰好消费一次,冥等。

offset:我们知道消费者需要记录消费进度,那么这个关键的属性就是offset,offset有手动提交和自动提交两种方式,而这两种方式和消息的前两种模式息息相关,本文暂不做解释。笔者采用的是自动提交的方式。

三,注解KafkaListener不起作用

笔者是采用springboot的方式使用kafka,具体搭建方法不了解可自行谷歌。而作为消费者处理通常为方法上注解KafkaListener。

现象:使用kafkamanager对comsumer进行查看:

% of partitions assigned to comsume 100

total lag 8

logsize 51 offset 43

同时,注解了KafkaListener的方法中的处理结果查询不到。再发送一条消息,发现lag和logsize均增加1,而offset依然为43。

一定是提交offset的设置改为手动提交了,而又粗心忘记写提交代码,这是笔者的第一反应,赶紧查看cloud config的配置文件,明晃晃地写着enable-auto-commit: true,一万点暴击。

笔者当然不会死心,第二个想法浮出脑海,处理超时导致无法正确提交offset,先改超时时长尝试,如果可以的话,处理改为压入任务队列或者线程池异步处理。一通修改和刷新操作,终于把cloud config改完了,然而理想丰满现实骨感,老问题依旧,再次一万点暴击。

最直接方式吧,远程调试,因为内测阶段可以任性。当然此步骤改为阿尔萨斯诊断也是可以的,只是略微繁琐些。注解的方法根本没有进去。这下一滴冷汗落在了地上,什么提交offset那都是镜中花水中月了。可是为什么comsumer是健康的状态呢。

查kafka和zookeeper的状态,重置manager,真是一顿操作猛如虎,现实结果二百五。

这下陷入好一阵子的迷茫,还好笔者不服输。查看和调试spring的源码,第一个断点选在了kafkacomsumer的方法poll中的comsumerrecords不为空的分支处,再次发送消息,命中断点!!心中总算泛起了一片曙光。单步进行调试,一开始的想法是一定是注解的方法没有被listener invoke。似乎一切都按照预想的步骤进行着

kafkacomsumer的poll→

KafkaMessageListenerContainer的invokeListener,而且records也是一个元素并且topic也正确,这又是为啥呢?我点开了records的元素,一个震惊的画面浮现在我面前key为null,value也是为null。豁然开朗,要么消息序列化失败,要么消息反序列化失败。查看序列化类kyro,笔者虽然蛮不喜欢这玩意,因为不跨语言,所以不如protobuf等,但是前任CTO选择的也无不可。先换吧,改成ObjectMapper的json先。结果出人意料还是不行!看来错怪了kyro。那么为啥呢,发送的对象是jsonobject,会不会和这个相关呢?抱着试试看的态度,换成class,成功了!!久违的结果啊。看了看手机已经很迟了,那还是先让大家回家吧。猜测和setAccessible有点关系,未证实,有答案的朋友要不吝赐教。共同进步。

到此问题终于解决了,希望读者也别再踏这个看似offset的坑。