消息队列。
why。
解耦。
eg. 物流系统发生故障,需要几分钟才能来修复,在这段时间内,物流系统要处理的数据被缓存到消息队列中,用户的下单操作正常完成。当物流系统恢复后,补充处理存在消息队列中的订单消息即可,终端系统感知不到物流系统发生过几分钟故障。
异步。
A 系统接收一个请求,需要在自己本地写库,还需要在 B、C、D 三个系统写库,自己本地写库要 3ms,B、c、D 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户体验非常不好,一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200ms 以内完成,对用户几乎是无感知的,如果用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。
如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,响应速度大大提升了,改善了用户的体验。
流量削峰。
应用系统如果遇到系统请求流量的瞬间猛增,有可能会将系统压垮。有了消息队列可以将
大量请求缓存起来,分散到很长一段时间处理,这样可以大大提到系统的稳定性和用户
体验。
一般情况,为了保证系统的稳定性,如果系统负载超过阈值,就会阻止用户请求,这会影响用户体验,而如果使用消息队列将请求缓存起来,等待系统处理完毕后通知用户下单完毕,这样下单体验要好。
流量削峰的经济考量。
业务系统正常时段的 QPS 是 1000,流量最高峰是 10000,为了应对流量高峰配置高性能的服务器显然不划算,这是可以使用消息队列对峰值流量削峰。
MQ 对比。
特性 | ActiveMQ | RabbitMQ | RocketMQ | kafka |
---|---|---|---|---|
开发语言 | Java | erlang | Java | Scala |
单机吞吐量 | 万级 | 万级 | 十万级 | 十万级 |
时效性 | ms 级 | us 级 | ms 级 | ms 级以内 |
可用性 | 高(主从架构) | 高(主从架构) | 非常高(分布式架构) | 非常高(分布式架构) |
功能特性 | 成熟的产品,在很多公司得到应用。有较多的文档。各种协议支持较好。 | 基于 erlang 开发,并发能力强,性能好,延时低。管理界面丰富。 | MQ 功能比较完备,扩展性好。 | 只支持主要的 MQ 功能,像一些消息查询、消息回溯功能没有提供,大数据领域应用广。 |
- 高可用。
RabbitMQ ~ 镜像集群。
RocketMQ ~ 双主双从。
消息丢失。
消息重复消费。消息幂等性问题。
消息重复的根本原因是网络不可达。
- 发送时消息重复。
当一条消息已被成功发送到服务端,此时出现了网络闪断,导致服务器对客户端应答失败。如果此时生产者意识到消息发送失败并尝试再次发送消息,消费者后续会受到两条同同样的消息。
- 消费消息时重复。
消息消费场景下,消息已经投递到消费者并完成业务处理,当消费方给 MQ 服务器反馈应答时网络闪断。为了保证消息至少被消费一次,MQ 服务器在网络恢复后再次场次投递之前已被消费方处理过的消息,此时消费者就会收到两条相同的消息。
- 解决。
- 消息发送者发送消息时携带一个全局唯一的消息 id。
- 消费者获取消息后先根据 id 在 Redis / db 中查询是否存在消费记录。
- 如果没有消费过就正常消费,消费完毕后写入 Redis / db。
- 如果消息消费过就直接舍弃。
消息消费顺序性。
一笔订单产生了 3 条消息,订单创建,订单付款,订单完成。
消费时要按照顺序依次消费。
与此同时多笔订单间又是可以并行消费的。
张三:订单完成,订单付款,订单创建。
李四:订单完成,订单付款,订单创建。
- 模型:M1 M2 按照先后顺序发送到 2 台 server,被两个消费者分别消费。(×)。
不能保证消息的顺序到达 MQ。
不能保证消息的顺序消费。
- 模型:M1 M2 按照先后顺序发送到 1 台 server,被两个消费者分别消费。(×)。
顺序到达可以保证。
不能保证顺序消费。
- 模型:生产者:MQ Server,消费者:1:1:1。
可以保证,但慢。(全局顺序性)。
吞吐量下降。
容错性降低。
- 模型 ~ 局部顺序消费。
生产者根据消息 ID 将同一组消息发送到一个 Queue 中。
多个消费者同时获取 Queue 中的消息进行消费。
MQ 使用分段锁保证单个 Queue 中的有序消费。
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3");
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt messageExt : list) {
// 业务处理。
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
MQ 实现分布式事务。
- 消息发送方。
处理业务逻辑。
保存消息到本地数据库。
发送消息给 MQ。
监听 MQ 消息方通知消息,更改消息状态为已处理。
定时任务将长期未处理的消息发送到 MQ。
- 消息消费方。
监听 MQ 中间件消息。
判断消息是否重复,重复就丢弃。
消息不重复,执行本地任务。
业务处理完毕,写消息记录到本地数据库。
发送通知消息到 MQ。