业务背景
重构项目,在测试环境联调,发布到一台服务器上启动。在app上触发一条消息到mq topic A,然后我这边消费消息,将消息通过微信推送给用户。
问题描述
触发消息后,结果却在1s内收到了两次微信通知,并且回执和库中却都收到了两条记录。
但是在服务器上面查找日志时只收到了其中的一次请求。
原因分析
触发了一条,却发了两条。百思不得其解?
于是就想到了其中一个方案,停了服务器上的服务后,再次触发,发现还会触发微信消息。那就说明有一台服务在跑。怎么找到这台服务?
想到mq监控台页面可以看到是哪些ip在连接。然后就通过对应的topic和消费者,直接查找到连接的实例ip。
果不其然,有个同事在20多分钟前本地启动了重构前的服务。而测试环境和本地的mq连接并没有隔离,并且重构前后的topic和消费者是一样的。
所以mq又被另一台消费了一次。
但是同一个topic和消费者,为什么会被消费两次呢?
RocketMQ提供了ack机制,以保证消息能够被正常消费。发送者为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功。中途断电,抛出异常等都不会认为成功——即都会重新投递。
经过分析发现:
这条消息先是被本地的服务给消费到了,但是本地的网络和服务器是比较卡的。
相当于网络是不稳定的,那么mq就没有收到成功的返回,那它就会重新发送到另外一台服务上了。
消息会被重复消费,那对客户的体验是很不好的。所以,幂等性校验还是很有必要的。
解决办法
RocketMQ不解决消息重复推送问题。所以需要自己处理。
-
每次消费数据,将唯一标识放入redis,key为唯一标识,value为incr自增。
过期时间设置在60s。
然后消费时看redis是否存在。
注意点:用redis的set和get时都做好分布式锁
-
可以用消息表做去重幂等操作:设置唯一索引,如果重复就不处理;
目前选择的是第一种。没有选择第二种的原因是:每天消费的数据量非常大,但重复消息的概率又非常低,如果直接操作表的话,会对系统的吞吐量有影响。
复盘
-
一个接口/服务被调用了两次,这台服务上没有,那么就要考虑是否有另一台在调用。
-
如何看到连接情况:直接通过界面上消费者来查看;
-
MQ会因网络抖动的情况出现消息重复消费的问题,所以要做好幂等校验;