分布式消息系统都具有高吞吐量、高可用性的特点,同时也离不开两个问题:
消息的顺序问题;
消息的重复问题;
1.顺序消息
消息有序:按照消息的发送顺序来消费。
(前提)对于M1与M2两条消息,要保证消息有序,需要将两条消息发送到同一个MQ Server上(因为两个无联系的MQ Server之间不能保证哪条消息先被消费);
而在实际情况中,会出现网络延迟问题,导致M2先到达消费端被消费,解决的方法是将M1和M2都发往同一个消费端,并且等M1响应成功后再发送M2;
如果发送M1之后无响应,为了保证消息一定被消费,一般会将M1重新发给另一个消费端(此处有重复消费问题);
以上总结:保证顺序消息最简单可行的办法就是保证生产-Server-消费的1对1对1关系,这种关系也存在严重的问题:
1.并行度成为消息系统的瓶颈,吞吐量不够;
2.更多的异常处理,比如消费端出现问题,就会导致整个处理流程阻塞;
而沈询说,世界上解决一个计算机问题最简单的方法:“恰好”不需要解决它!
因为我们可以通过合理的设计和将问题分解来规避,不需要去死抠。基于从业务层面保证消息的顺序而不仅仅依赖于消息系统,
RocketMQ的解决办法如下:
通过轮询所有队列的方式确定消息被发往哪个队列(负载均衡),比如订单号相同的消息被先后发送到同一个队列,保证消息被顺序消费;代码中可以通过MessageQueueSelector的具体实现确定具体的选择策略;
2.消息重复
造成消息重复的根本原因:网络不可达。只要是通过网络交换数据,就不可能避免这个问题,所以解决这个问题的方法就是绕开这个问题,那么这个问题就变成了:如果消费端得到了两条同样的消息,应该怎样处理。如下两个方法:
1.消费端处理消息的业务逻辑保持幂等性(处理多少次都是同样的结果);
2.保证每条消息都有唯一编号并且消息处理成功与去重表日志同时出现(在处理之前先在去重表中查询,如果发现去重表中已经有对应编号,则跳过处理);
第一种方式很明显需要在消费端做处理;第二种方式可以在消息系统也可以在消费端做处理,由于实际情况中出现重复消息的概率很小,RocketMQ为了保证高吞吐量和高可用,不对消息重复进行处理(RocketMQ并不保证没有重复消息),如果业务需要严格的消息处理逻辑,需要在消费端的业务逻辑中进行处理;
3.事务消息
RocketMQ支持事务消息。单机上高效的事务逻辑,在集群环境中会因为网络通信的原因而变得缓慢,解决办法就是:大事务=小事务+异步,具体来说,将一个大事务拆分成不同机器上的小事务,之间通过异步通信进行连接(大事务中包含网络通信,小事务不包含网络通信),并且小事务与异步通信的发送应该同时成功或同时失败;
RocketMQ事务消息的设计,主要是为了解决Producer端消息发送与本地事务执行的原子性问题。RocketMQ设计中broker与producer端的双向通信能力,使得broker天生可以作为事务协调者存在;RocketMQ本身提供的存储机制,为事务消息提供了持久化能力;RocketMQ的高可用机制以及高可靠性设计,则为事务消息在系统发生异常时,依然能够保证事务的最终一致性。
为了保证本地事务与消息发送的原子性,如果是用Spring来管理事务,可以将消息发送放到本地事务中(“恰好”规避问题),如果是在RocketMQ环境下,处理方式如下:
1.发送prepared消息,Producer可以根据返回的sendResult得到事务ID;
2.处理本地事务;
3.发送确认消息,更新事务ID对应的事务消息的状态;
4.如果确认消息发送失败,broker会定期(默认1分钟)对消息集群中的事务消息进行轮询,如果发现了prepared消息,会向producer发送确认消息,producer会根据本地事务的执行结果以及发送消息的策略决定回滚还是继续发送确认消息,从而保证本地事务与消息发送同时成功或失败;
RocketMQ事务相关源码(3.2.6版本):
TransactionCheckListener用于实现发现prepared消息时的处理策略;
TransactionMQProducer用于构造事务消息的生产者;
producer.setTransactionCheckListener用于设置事务决断处理类;
TransactionExecuterImpl用于设置本地事务处理逻辑;
producer.sendMessageInTransaction用于发送消息(包含了发送prepared消息、执行本地事务、发送确认消息三个步骤);
本地事务处理完成并且异步消息发送成功后,消费端开始消费消息。消费时会出现消费超时和消费失败的问题,对于消费超时,处理办法就是一直重试,此时可能出现重复消息的问题,按照上面的办法处理即可;对于消息失败的问题,RocketMQ给出的处理办法是人工解决,因为按照事务的流程,此时需要回滚整个流程,而这是一个很复杂的过程,很容易出现bug,对于这种小概率的错误,并不适合花大心思去解决,所以RocketMQ将问题保留。
转载于:https://www.cnblogs.com/yuanfei1110111/p/10135454.html