普通消息、顺序消息的区别,在什么场景会用到
普通消息,就是很普通,没有什么特别的, 和顺序消息相比的话,就是消息发送到 MQ 后,消费者不保证严格按照发送顺序消费。消息可能会被乱序消费。
顺序消息是指生产者按照顺序发送消息,消费者也必需严格按顺序消费的消息。一般是通过绑定同一个分区来实现的。
顺序消息一般用在对顺序性有严格要求的场景中,比如是涉及到状态流转,尤其是那种状态流转的时间间隔很短的。
比如贷款的还款成功消息和结清消息。交易的确认收货消息和订单完结消息,基本都是同时发生的。如果系统是分别要处理这两个消息,那么可能就需要先处理确认收货,再处理订单完结,否则就会出问题的。
但是大多数情况下,我们用顺序消息的并不多,因为这只对收消息的应用有好处,而对发送消息的应用没有什么好处,反而增加了复杂性(而且还有吞吐量低、维护成本高等缺点)。而要做顺序消息,又需要发送者来做代码改造,所以很多系统都不愿意改,比如交易系统,不可能这么改的。
所以,实际上的大多数场景,并不一定会直接用MQ的顺序消息,而是倾向于消费者自己排序
前置状态判断
作为一个消费支付消息和发货消息的系统,我们可以基于这个 beforeStatus 来判断和我系统中的当前状态是否一致,如果是一致的,说明我是可以处理的,那么我就处理这个消息,如果是不一致的,那说明我要的消息还没来,那我就把这个消息处理失败,让 MQ 下次再重投给我。
这个方案有两个要求:
1、消息要能推进单据状态,比如支付消息可以把订单从待支付推进到已支付。
2、消息的状态是单向的,不能出现那种从待支付推进到已支付了,过了一会又变成待支付了。
这样就能通过状态来确保消息的有序。
增加序列号
如果无法完全保证发送和处理的顺序,又没有状态来做前置判断,可以在消息中引入序列号,消费端根据序列号重排。例如:
在消息中附加一个递增的序列号。
消费端使用缓冲区缓存收到的消息,根据序列号重新排序后再处理
缺点是会增加系统复杂度,并且需要设置缓存超时时间来处理丢失的消息。
…
第二个方案存在一个问题,那就是依赖MQ的重新投递,有可能会导致最终这个消息丢失了,因为一旦长时间无法消费,消息就会不再重投了。而且不断地让MQ重试,也可能会导致消息堆积,并且对系统造成一定的压力。
第三个方案的问题就是需要再内存中维护一个队列,来进行排序,太麻烦了
自己实现排序
- 接到消息之后,做基本的前置校验,如消息幂等、参数齐全等,如果校验不通过,直接返回失败。
- 一旦消息校验成功,把消息体转成一个内部的事件,这个事件是自己定义的,方便后续解析和处理。
- 把这个事件存到数据库中,状态设置为待处理。
- 如果数据库保存成功,返回消息处理成功。
- 在第四步返回之前,开启异步线程处理这个事件,执行他需要执行的代码,如果成功,则把消息状态设置为已处理。如果没成功,不用改消息状态(或者改为失败也可以),然后在执行次数上 +1
- 起一个异步任务,定时扫描事件表中的未成功的事件进行重试。
这么做,就能确保所有的事件我都有存储,并且存储后立刻返回,避免消息重投和堆积。消息存储下来之后,我就可以基于这些消息做排序,以及重试了,如果某个消息处理失败了,也不怕,不断重试即可。当达到了一定次数之后,报警出来人工跟进。
