消息中间件的三个使用场景

削峰

瞬时流量很大,可以把请求放到MQ中,然后慢慢的从MQ中消费消息。

异步

例如一个场景,下单之后给订单服务、物流服务、库存服务发送下单的消息,如果没有MQ,那么需要一个个调用这些服务的接口,而如果是用MQ,那么只需要把下单的消息发到MQ中,订阅的服务自己取消息即可。

解耦

还是上面的场景,本来下单操作的服务需要调用订单服务、物流服务、库存服务等的接口,而用MQ只需要和MQ交互就可以了。

顺序消费

把需要有顺序要求的消息发送到一个队列中即可。例如同一个订单的消息,可以通过对订单号进行hash取模的方式,这样这个订单的消息都可以发到同一个队列。

幂等消费

rocketMQ的幂等消息分为两部分:一个是生产者到broker,一个是broker到消费者。

上半场幂等

生产者发送消息到broker步骤是这样的:

  1. producer发送消息到broker;
  2. broker消息落地;
  3. broker返回ACK给producer。

如果在消息落地之后出现异常,那borker没有返回结果给producer,这时producer会重试发送,就可能出现重复消息的情况。RocketMQ在broker中实现了全局唯一的消息ID,通过这个唯一的消息ID就可以实现幂等了。

下半场幂等

broker发送消息到消费者步骤是这样到:

  1. borker发送消息到消费者;
  2. 消费者进行业务处理,并返回ACK给producer;
  3. 消息偏移量调整。

这时候如果消费者消息处理失败,broker会发送重试消息,所以消费者这里需要做幂等处理。这里一般就是需要我们业务上保证幂等。幂等的意思就是,多次处理结果是一样的。所以读的场景就是幂等的,而对于写的场景需要一些方式去保证多次处理结果相同,例如在插入数据之前,先查询数据是否存在,或者更新的时候,把字段A更新成值value。

消息不丢失

从消息发出到消息消费结束,都需要保证消息不丢失。所以我们从三个部分来考虑。

producer->broker

这个阶段消息丢失一般是说,producer以为把消息发送出去了,但其实borker并没有保存住这个消息,所以我们在发送了之后需要关心发送的结果,rocketMQ提供了producer->broker的确认机制,通过这个来保证消息的可靠性。而produer消息发送一般有三种方式:同步、异步和单工。为了保证消息的可靠性,我们一般会用同步或异步的方式来方式。

如果broker没有返回结果,那超时之后producer会重发消息,这就是前面上半场消息幂等的情况。

broker消息落地

如果说,消息在broker上是存在内存的,那么broker宕机,消息就丢失了,此外,一般broker是采用主从的方式建设的,如果主从同步的时候出现问题,也可能导致从机上丢失消息。
所以为了保证消息的可靠性,broker的消息落盘方式有两种:同步刷盘和异步刷盘。其中同步刷盘的方式可靠性更高,但效率更差,因为消息过来就要刷到磁盘,本来的话可以刷到page cache就可以了。主从同步的方式也有两种:同步复制和异步复制,同样的,同步复制的方式可靠性更高,但性能更差。综合考虑大多会用异步刷盘和同步复制的组合。

broker->consumer

broker消息发送到consumer,如果consumer消费失败返回 COUNSUMELATER,那么会把这条消息返回给broker,放入重试队列(RETRY开头的topic),后面会让重试队列中的消息再发给consumer,如果重试了16次还是失败,会放入死信队列(DLQ_开头的topic),通过这种方式就算消费失败了,消息也没有丢失,我们还是可以回溯到的。
如果consumer消费返回 CONSUME_SUCCESS,那么broker可以提交位点。如果consumer超时,不返回结果,那么broker还会再发这条消息进行重试。

消息堆积

如果消费能力小于生产能力,那就会造成消息堆积。所以解决方式也很简单,增大消费能力,例如消费端用多线程去处理,或者增加consumer集群数量等。或者在producer端或broker进行消息的过滤。

事务消息

总结来说,可靠消息最终一致性实现的原理有两点:事务消息和rocketMQ消息可靠性的保证。

  1. 半消息(事务消息)的存在,生产者可以先发一个消息到broker,但这个消息没提交之前,不会被consumer消费到。然后broker收到消息之后返回确认给生产者,生产者再执行本地事务。
  2. 如果生产者本地事务执行成功,提交之前的事务消息,如果生产者本地事务执行失败,就回滚事务消息,那也就代表本地事务结束。
  3. 生产者提交事务消息之后,broker就可以把这个消息发给消费者,那这个时候的由于rocketMQ可靠性的保证,这条消息其实就不会丢了,所以就算消费者那里消费消息,执行本地事务失败,这个消息也会通过重试机制来不断尝试,在重试了16次之后,还会放入死信队列,后面开发可以去看死信队列的失败消息,人工兜底,完成本次事务的处理。

消息消费两种方式

MQ消费者一般有两种消息消费方式;push和pull。

消费方式 特点 优点 缺点
push broker主动推给消费者 实时性高 数据量大可能压垮客户端
pull 消费者自己去broker取消息 客户端根据需要自己去拉,不会压垮服务 不知道消息什么时候来,实时性差

rocketMQ采用的是一种长轮询的pull方式。客户端主动去拉消息,如果有消息返回消息,如果没有消息,broker会bold住请求15秒,在这时间内如果有新的消息过来,那么会把消息返回给客户端,这样实时性更好。

广播模式和集群模式

consumer可以指定消费模式 consumer.setMessageModel,默认就是集群模式,也就是一个consumer group的消费者共同消费一个topic,而如果是广播模式,那么一条消息会发给consumer group中的所有消费者。

此外如果我们要让一个topic的消息让多个消费者消费,处理广播模式之外,还可以创建多个consumer group,这样订阅这个topic的consumer group也都会消费消息。

几种MQ的对比

MQ 优点 缺点
kafka 吞吐量高,百万级的TPS 队列多了性能差,没有事务消息
rocketMQ 支持队列更多,性能好,吞吐量十万级