提到消息队列,相信大家都不陌生,本文主要在于抛砖引玉,简要介绍一下消息队列的几个问题
1.可靠性
消息队列的可靠性怎么保证,一般分为三段 producer --> broker,broker自身,broker --> consumer
1) producer -- > broker
问题: 生产者写消息存在丢失或者MQ接受存在问题
解决方案
以rabbitMQ为例
a. 采用事务
rabbitMQ客户端与事务相关的方法有: channel.txSelect、channel.txCommit以及channel.txRollback。channel.txSelect用于将当前的信道设置成事务模式,channel.txCommit用于提交事务,而channel.txRollback用于事务回滚。可以在发送中进行捕获异常,如果出现未接受异常进行回滚操作。
缺点: 耗费性能
b. confirm机制
rabbitMQ生产者将信道设置成confirm(确认)模式,一旦信道进入confirm模式,如果接受到消息,回调这个接口,接受成功。如果没接收,调用回调接口,接收失败。
2) broker自身
问题: MQ接受到数据先暂存在内存中,消费者还没有消费,MQ挂掉,缓存丢失。
解决方案:
以rabbitMQ为例,需要设置队列持久化和消息持久化,缺一不可,同时刷磁盘需要一段很短的时间,如果这段时间内服务节点发生宕机、重启等问题,消息还是会丢失,而第一点中提到的rabbitMQ的confirm机制,服务端的返回是在消息落盘之后执行的,这样可以进一步的提高了消息的可靠性。更进一步的化可以配置镜像队列,做多机房容灾等
3) broker --> consumer
问题: 消费拉取到消息还没处理直接挂掉,但MQ以为对方收到消息。
解决方案
以rabbitMQ为例,提供了消息确认机制(ack)。消费者在订阅队列时,可以指定autoAck参数,当autoAck等于false时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除)。当autoAck等于true时,RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正的消费到了这些消息。
2.顺序性
有一些场景,比如交易的场景,一个订单,有3个阶段,订单创建,订单付款,订单完成,那么这三个阶段,对应有相应的后续操作,如果不能保证3个消息的顺序性,可能就会出现系统乃至业务上的错误,消息队列如何保证顺序性呢
以上图为例,其实就是保证1个订单的三个消息的顺序性,首先producer --> broker保证同一订单的这三个消息不会错乱,然后到了broker端,将其路由(hash)到一个队列或者partition中,再到broker --> consumer中,消费端保证单线程消费,同时内部使用同样的hash到多个内存队列,多线程处理即可
3.幂等性
幂等性为什么重要,这个不用说大家想必都会想到,特别是对于交易支付的场景,重复的操作和行为会导致重复扣款,重复支付等资金风险,而对于消息队列,消息重复可以说是不可避免的,除去系统bug等人为错误,就网络问题这一项,也难以避免消息不会重复,业界的消息队列一般都遵循at least once的原则,所以做好幂等性是必须的。通常的幂等,都在消费端根据业务的形态来做
4.事务消息
分布式场景下利用消息队列处理分布式事务,通常有两种做法
1)中间件自身解决
何为中间件自身解决,就是说中间件自带事务消息,举例:RocketMQ的事务消息
事务消息发送及提交:
1.1 发送消息(half消息)
1.2 服务端响应消息写入结果
1.3 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
1.4 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
补偿流程:
1.5 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
1.6 Producer收到回查消息,检查回查消息对应的本地事务的状态, 根据本地事务状态,重新Commit或者Rollback
补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
2)业务解决
主要是额外的设置一个消息表,将业务处理和插入消息绑定到本地事务里,然后利用消息队列发送消息返回的ack来删除消息表里的消息,如果没有返回ack,本地会有一个task不断的轮训消息表保证消息发送成功后删除,也就是事务处理和插入消息一起成功后,保证消息一定发送成功
本文主要还是抛砖引玉,至于这几个方面的问题映射到业界的MQ里的实现,未做解释和对比,后续可能会逐一对比
网友评论