消息中间件的三个使用场景
削峰
瞬时流量很大,可以把请求放到MQ中,然后慢慢的从MQ中消费消息。
异步
例如一个场景,下单之后给订单服务、物流服务、库存服务发送下单的消息,如果没有MQ,那么需要一个个调用这些服务的接口,而如果是用MQ,那么只需要把下单的消息发到MQ中,订阅的服务自己取消息即可。
解耦
还是上面的场景,本来下单操作的服务需要调用订单服务、物流服务、库存服务等的接口,而用MQ只需要和MQ交互就可以了。
顺序消费
把需要有顺序要求的消息发送到一个队列中即可。例如同一个订单的消息,可以通过对订单号进行hash取模的方式,这样这个订单的消息都可以发到同一个队列。
幂等消费
rocketMQ的幂等消息分为两部分:一个是生产者到broker,一个是broker到消费者。
上半场幂等
生产者发送消息到broker步骤是这样的:
- producer发送消息到broker;
- broker消息落地;
- broker返回ACK给producer。
如果在消息落地之后出现异常,那borker没有返回结果给producer,这时producer会重试发送,就可能出现重复消息的情况。RocketMQ在broker中实现了全局唯一的消息ID,通过这个唯一的消息ID就可以实现幂等了。
下半场幂等
broker发送消息到消费者步骤是这样到:
- borker发送消息到消费者;
- 消费者进行业务处理,并返回ACK给producer;
- 消息偏移量调整。
这时候如果消费者消息处理失败,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消息可靠性的保证。
- 半消息(事务消息)的存在,生产者可以先发一个消息到broker,但这个消息没提交之前,不会被consumer消费到。然后broker收到消息之后返回确认给生产者,生产者再执行本地事务。
- 如果生产者本地事务执行成功,提交之前的事务消息,如果生产者本地事务执行失败,就回滚事务消息,那也就代表本地事务结束。
- 生产者提交事务消息之后,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 | 支持队列更多,性能好,吞吐量十万级 |