消息队列发送消息
MQ发送消息有三种实现方式
- 同步可靠发送
- 异步可靠发送
- 单向不可靠发送
同步可靠发送
原理:同步可靠发送是指发送方发出数据后,会等待直到接收方发回响应后才发出下一条消息。如下所示(图来自消息队列MQ新增3把武器)
应用场景:此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等。
异步可靠发送
原理:异步可靠发送是指发送方发出数据后,不等待接受方发回响应,接着发送下一个消息。其可靠保障主要是需要发送方发送异步回调接口(SendCallback),在服务器接受到消息后回调该接口发送接收响应。如下所示(图来自消息队列MQ新增3把武器)
应用场景:异步可靠发送一般用于链路耗时较长,对RT响应时间较为敏感的业务场景,例如用户视频上传后通知启动转码服务,转码完成后通知推送转码结果等。
单向不可靠发送
原理:单向不可靠发送特点时只负责发送消息,不等待服务器回应其也没有回调函数的触发,即只发送消息不等待应答。该方式发送消息过程耗时非常短。如下所示(图来自消息队列MQ新增3把武器)
应用场景:适用于某些耗时非常短且对可靠性要求不高,但对可靠性要求并不高的场景,例如日志收集。
三种方式的特点和区别如下:
方式 | 发送TPS | 发送结果反馈 | 可靠性 |
---|---|---|---|
同步可靠发送 | 较快 | 有 | 不丢失消息 |
异步可靠发送 | 快 | 有 | 不丢失消息 |
单向不可靠发送 | 最快 | 无 | 可能丢失消息 |
消息队列接收消息
MQ接收消息也有同步和异步的区分,这里主要讲异步的方式,其中异步主要有下面两种方式:
- Push Message(推消息)
- Pull Message(拉消息)
这里不管是推还是拉,都是异步情况下。该情况下,MQ服务器异步发送消息给客户端,客户端在接收消息后会发送ACK消息给MQ服务器表示已经接收到ACK消息后删除消息,这样保证消息的可靠到达。
推、拉消息
推消息(Push)主要指MQ-Server主动把消息推送给消费者,拉消息(Pull)主要指消费者主动向MQ-Server拉消息。Push、Pull主要是有以下优缺点:
1. 慢消费
慢消费指的是消费者消费消息的速度达不到生产者生成消息的速度。慢消费会造成的MQ-Server消息积压,在Push模型下MQ-Server会一直不断的向消费者发送消息,造成网络积压。而Pull模型下由于是消费者主动向MQ-Server拉消息,所以不会造成网络积压。
2. 消息延迟与忙等
消息延迟和忙等主要是Pull模型造成的问题。Push模型主动权在MQ-Server这里所以不会造成上诉问题。而由于Pull模型主动权在消费者,消费者会主动向MQ-Server拉消息,所以出现消费者什么时候去Pull消息,所以会操作消息延迟。在RocketMQ中,其使用一种长轮询的做法去Pull消息。基本思路是:消费者尝试拉消息,如果拉取失败则会把该连接wait,直到有新消息来然后notify返回新消息。
消息队列架构
下图是MQ的核心架构图(图来自微信公众号<架构师之路>),注:都是异步可靠方式
消息队列核心架构分为3块
- 发送方:红色区域,其由MQ-Client和业务调用模块构成
- MQ服务器:蓝色区域,其指的是Broker(消息中间件代理)
- 接收方:黄色区域,其由MQ-Client和业务调用模块构成
发送方由业务调用模块和MQ-Client-Sender组成,后者向前者提供两个核心API:
- sendMsg(byte[] msg)
- sendCallback(Callback callback)
接受方由业务调用模块和MQ-Client-Receiver组成,后者向前者提供两个核心API:
- ReceiveMsgCallback(byte[] bytes)
- sendAck()
这里有点像设计模式中的事件驱动模式,当有MQ-Client-Sender、MQ-Client-Receiver时就相当于向MQ-Server注册这两个事件。当MQ-Server接收到消息时回调MQ-Client-Sender事件接口以告知MQ-Client-Sender已经接收到该消息,当MQ-Server发现符合MQ-Client-Receiver的消息时,就回调MQ-Client-Receiver事件接口以发送消息。
消息队列消息可靠性的保证
MQ需要接收生产者生成的消息并把消息传递给消费者进行消息消费,所以MQ需要保证消息发送和消息传递的可靠性。注:图来自微信公众号<架构师之路>
消息发送
- MQ-Client将消息发送给MQ-Server(业务方调用sendMsg())
- MQ-Server将消息落地(就是存放消息),落地后即为发送成功
- MQ-Server发送应答给MQ-Client(MQ-Server回调Callback)
消息传递
4. MQ-Server将消息发送给MQ-Client(MQ-Server回调ReceiveMsgCallback接口)
5. MQ-Client发送应答ACK给MQ-Server(业务方调用sendAck())
6. MQ-Server接收到ACK,将之前落地的消息删除
由上我们可以看出消息发送和消息传递过程中都可能发送消息丢失,所以我们为了保证消息的可靠性,MQ需要进行消息重传和超时
消息发送的超时和重传
在消息发送过程中1、2、3可能出现消息丢失或者超时,由于MQ-Client没有收到回调消息,所以MQ-Client会在time时间内重发消息直到收到回调消息。需要注意的是,这里可能造成MQ-Server收到同一消息的多次重发,也就是造成了消息重复。
消息传递的超时和重传
在消息传递过程中的4、5、6可能出现消息丢失或者超时,由于MQ-Server没有收到Ack消息所以会造成MQ-Server重发消息,也就是造成了消息重复。
也就是说:由于要保证消息的可靠性会导致消息重复
消息去重
由上面我们可知在保证消息的可靠性上回导致消息的重复,并且多条消息body可能相同,所以需要我们解决下面两种问题:
- 避免消息重复消费
- 避免多条相同body的消息被过滤
自己在网络上看了许多的方案,发现消息去重一般由业务端自己去解决,主要保证消费者消费消息的幂等性
消费消息的幂等性
我们可以下生产者生成消息时在消息体中插入去重key,在消费者消费消息时,通过去重key保证相同消息不会被重复消费并且保证相同body的消息不会被过滤。其中去重key可以由<生产者IP+线程ID+时间戳+时间内递归值>组成唯一值。具体可见:腾讯云关于消费去重文章
Reference
http://jm.taobao.org/2016/07/21/3-ways-to-send-message
https://tech.meituan.com/mq-design.html
https://cloud.tencent.com/document/product/406/4791
架构师之路工作号《消息总线能否实现消息必达?》(由于微信公众号文章URL有时间限制,所以没有列出来)