
消息队列在复杂系统中应用广泛。虽然说加入mq后系统的复杂度提高、系统可用性也变低而且可能引发数据出现一致性的问题那么为什么还要用MQ呢其实主要是其在特殊的场景下能解决我们很多问题。一、MQ使用的主要场景使用MQ主要场景有三种解耦、异步以及削峰。下面就三大使用场景进行简单介绍1、解耦以生产者-消费者模式为例系统A要为系统B、C发送消息A不仅要维护现有接入系统的数据接受是否超时、失败重发等情况还要维护新增接入系统的D、或者系统B不需接收消息等动态变化的情况。那么MQ的“发布-订阅消息模型”就可以把A与其他系统间的通信进行解耦。这个场景下主要考虑点是消息的调用是否需要直接同步不需要的话即可使用MQ进行系统解耦。2、异步在完成一个业务流程中各个结点不一定需要全部是同步进行的。比如刷卡付款和短信通知扣款这就是可以异步进行的业务节点。日常开发中我们也经常碰到系统A收到请求报文后进行本地入库后给其他两个系统B、C分别发消息然后等待全部消息被处理返回后在返回给用户。假设A入库需要10msB、C返回结果分别是500ms那么总耗时就是10ms500ms500ms1010ms。使用MQ,A发消息给MQMQ同步给B和C。总耗时最多也是10ms500ms510ms几乎效率提升了一倍。3、削峰一些电商平台的流量在一天内会出现明显的抖动。在平常并发数大概也就50个/秒但是高峰期可能达到5000以上的并发。而这些平台的数据库都是并发支持上限在2000左右的mysql因此很快久把数据库给搞崩了系统也没法再正常使用。常用的做法是把请求写入到MQ里面然后系统A按数据库可以支持的并发量去拉取数据。这样就限制住了整个系统的处理速率等待高峰期过后系统访问流量下降到50个时依旧用2000个/秒的处理速度即可快速消化积压的请求。二、MQ系统设计RabbitMQ是一款常用的MQ中间件我们就以它为例讲述下MQ系统设计的过程。我们了解MQ是基于“发布-订阅消息模型”设计的那么这里就有三个主要的节点消息队列、生产者、消费者。上一节我们也说过了解耦、异步、削峰的三大使用场景或者好处但是坏处在引起系统可用性降低、系统复杂度提高以及数据一致性问题。我们设计时前面两个问题比较难解决我们重点解决一致性问题。接下来我们就从消息队列、生产者、消费者进行优化设计1、消息队列RabbitMQ的信息在默认情况下只保存在内存中不做持久化到硬盘的操作。这种情况下如果出现MQ节点宕机或者重启的话消息就被丢失了。所以我们设计时要考虑把消息进行持久化。从RabbitMQ的架构图de 架构图来看要实现消息持久化需要同时满足三个条件1Exchange设置了持久化2Queue设置持久化3Message持久化2、生产者在生产者这端遇到的主要问题是不能确定消息是否真的到达了MQ服务器并被正常接收。对于一般消息来说丢失了影响可能不大但是对于类似银行卡的账号余额变动扣款等消息那么影响就非常大了。那么我们可以在生产者端设置Confirm和Return两种机制。在配置文件中加入publisher-confirm-type: CORRELATEDpublisher-returns: true3、消费者在正常流程中只要RabbitMQ把消息推送给了消费者即可认为投递成功那么MQ就会把内存或者磁盘中的消息给删掉。但是如果消费者节点意外挂掉了如逻辑处理时间过程超时了、网络中断、消费者节点被停掉等多种异常情况。那么消息可能就被丢失了没法正常消费掉。所以我们可以把消费者端的自动ACK模式改为手动ACK模式。修改后消费者在处理消息成功后手动ACK给MQ服务端这时候服务端才把消息从内存删掉。如果一个消费者端出问题了没有消费成功这个消息那么消息还可以给其他同功能的消费者去进行消费保证消息不丢失。这种机制就叫ACK确认机制。4、应对意外情况上面从发布-订阅消息模型中的三个节点进行MQ系统设计的优化可以保证99.99%的消息被正常处理但是还是有一些常见的意外场景值得我们在深思的。例如生产者在发送消息到MQ前突然宕机MQ在准备持久化到磁盘时宕机。那么我们应该如何应对呢消息补偿机制就是一个很好的解决方法。一般会抽取一个独立的的微服务定时轮询数据库中消息发送情况并把未发送成功的消息进行重新发送。一般消息持久化入库会设定补偿次数创建时间、以及消息状态。消息补偿服务会定时把补偿次数小于阈值的未发送成功的信息进在行重发。当然也需要预留业务处理的事件一般情况下我们可以设定创建时间在5分钟内的消息不进行重发。