事故现场分析:
由于创新业务产品上线,运营产品想通过一些活动来刺激用户,采用注册邀请机制即可获取积分的相关活动。考虑到后续可能还有其他可能的活动来发放积分,所以设计的时候,采用mq消息模式发放积分,异步解耦,并能够保证数据的最终一致性。考虑到用户积分计算的时候可能存在并发操作的情况,想到两种解决方案:
1.采用分布式锁的方式,计算用户积分,同一个用户操作时保持同步处理
2.采用队列的先天优势,FIFO的特性,只要保证同一个用户的积分放在同一个队列下,队列能够顺序消费即可保证用户积分计算同步进行。
经过思考和筛选最终决定采用RocketMQ队列FIFO计算积分,所以不知不觉已经深陷泥潭。由于公司公司封装的MQ消息处理并没有做消息的顺序发送和消息的相关封装,于是自己在公司公共的方法上重新封装了一层顺序发送消息的方法,采用用户唯一Id的hash求余选择发送队列,保证了同个用户的积分消息能够发送到同一个队列之中,然后想当然的认为MQ在消费消息的时候,是取一条消费一条SUCCESS
后再去消费其他的队列消息。于是开发完成就提测了,结果测试在做并发测试的时候用户积分计算总是比实际发放的积分数要少。于是,开始陷入沉思······
事故排查:
于是开始怀疑自己的代码问题,查呀查呀······逻辑一点问题没有。于是开始了猜想:
1.难道是消息发送丢失?或者发送的时候不在同一个队列?
于是暴力打印消息发送日志,经过查看日志,发现消息发送和消费一切正常。并且根据Hash算法同一个人也在同一个队列里面,但是为什么还是出现并发计算了呢?
2.消息消费的时候是并发消费的?
由于之前对RocketMq的熟练度不够,不清楚原来顺序消费也要用方法保证的采用MessageListenerOrderly
这个listen消费的才能能保证消息的顺序消费,于是翻看公司封装的消息方法采用的是DefaultMessageListener
。broker并不能完全保证消息的顺序消费,它仅仅能保证的消息的顺序发送而已!,只要顺序的发送,再保证一个线程只去消费一个队列上的消息,那么他就是有序的。
解决方案:
对公司的MQ方法封装,做了二次封装采用用户唯一ID的Hashcode
求余选择队列并采用MessageListenerOrderly
监听和消费消息,保证消息消费的顺序性。测试并发计算解决,积分计算问题完全正确。
网友评论