前言:
此文章是在学习极客时间消息队列高手课程的时候整理而来,做了一点扩展,主要目的是方便复习扩展,还是建议去看原课程,讲的很好。
01 | 为什么需要消息队列?
主要功能:收发消息,解决应用之间的通信问题。
哪些问题适合使用消息队列来解决?
1、异步处理
例如秒杀处理中确定本次请求的秒杀结果后,就可以马上给用户返回响应,然后把请求的数据放入消息队列中,由消息队列异步地进行后续的操作。
优点:
- 更快返回结果;
- 减少等待,实现了步骤之间的并发,提升系统总体的性能。
2、流量控制
使用消息队列隔离网关和后端服务,以达到流量控制和保护后端服务的目的。
- 网关在收到请求后,将请求放入请求消息队列;
- 后端服务从请求消息队列中获取 APP 请求,完成后续秒杀处理过程,然后返回结果。
优点:
- 大量的秒杀请求到达网关时,不会直接冲击到后端的秒杀服务,而是先堆积在消息队列中,后端服务按照自己的最大处理能力,从消息队列中消费请求进行处理。
- 根据下游的处理能力自动调节流量,达到**“削峰填谷”**的作用。
- 可以随时增加秒杀服务的实例数量进行水平扩容,而不用对系统的其他部分做任何更改。
代价:
- 增加了系统调用链环节,导致总体的响应时延变长。
- 上下游系统都要将同步调用改为异步消息,增加了系统的复杂度。
更简单的流量控制方法:令牌桶
单位时间内只发放固定数量的令牌到令牌桶中,服务在处理请求之前必须先从令牌桶中拿出一个令牌,如果令牌桶中没有令牌,则拒绝请求。保证单位时间内,能处理的请求不超过发放令牌的数量,起到了流量控制的作用。
不需要破坏原有的调用链,只要网关在处理 APP 请求时增加一个获取令牌的逻辑。
实现:
用固定容量的消息队列加一个“令牌发生器”来实现:令牌发生器按照预估的处理能力,匀速生产令牌并放入令牌队列(队列满了则丢弃令牌),网关在收到请求时去令牌队列消费一个令牌,获取到令牌则继续调用后端服务,获取不到令牌则返回失败。
3、服务解耦
引入消息队列后,订单服务在订单变化时发送一条消息到消息队列的一个主题 Order 中,所有下游系统都订阅主题 Order,这样每个下游系统都可以获得一份实时完整的订单数据。
无论增加、减少下游系统或是下游系统需求如何变化,订单服务都无需做任何更改,实现了订单服务与下游服务的解耦。
4、其他应用场景
- 作为发布 / 订阅系统实现一个微服务级系统间的观察者模式;
- 连接流计算任务和数据;
- 用于将消息广播给大量接收者。
5、问题和局限性
- 引入消息队列带来的延迟问题;
- 增加了系统的复杂度;
- 可能产生数据不一致的问题。
02 | 该如何选择消息队列?
及格的标准:开源、有一定社区活跃度、与周边生态系统有比较好的集成和兼容
- 消息的可靠传递:确保不丢消息;
- Cluster:支持集群,确保不会因为某个节点宕机导致服务不可用,当然也不能丢消息;
- 性能:具备足够好的性能,能满足绝大多数场景的性能要求。
1、RabbitMQ
轻量级、迅捷、开箱即用、RabbitMQ 是一个相当轻量级的消息队列,易部署和使用。
特点:
- 支持灵活的路由配置,它在生产者(Producer)和队列(Queue)之间增加了一个 Exchange 模块,你可以
理解为交换机。可根据配置的路由规则将生产者发出的消息分发到不同的队列中。可以自定义路由规则。 - 支持的编程语言大概是所有消息队列中最多的。
问题:
- 它的设计理念里面,消息队列是一个管道,大量的消息积压是一种不正常的情况,应当尽量去避免。当大量消息积压的时候,会导致 RabbitMQ 的性能急剧下降。
- 性能差,每秒钟可以处理几万到十几万条消息。
- 编程语言 Erlang小众,难以扩展和开发。
2、RocketMQ
特点:
- 有着不错的性能,稳定性和可靠性;
- 有非常活跃的中文社区,易于维护;
- 使用 Java 语言开发,易对 RocketMQ 进行扩展或者二次开发;
- 响应时延做了很多的优化,大多数情况下可以做到毫秒级的响应,很在意响应时延,那应该选择使用RocketMQ;
- 性能比 RabbitMQ 要高一个数量级,每秒钟大概能处理几十万条消息。
问题:
- 国际上还没有那么流行,与周边生态系统的集成和兼容程度要略逊一筹。
3、Kafka
Kafka 最初的设计目的是用于处理海量的日志。
特点:
- 有不错的数据可靠性、稳定性和功能特性;
- 与周边生态系统的兼容性最好,尤其在大数据和流计算领域,相关开源软件系统都会优先支持 Kafka。
- 设计上大量使用了批量和异步的思想,这种设计使得Kafka 能做到超高的性能。
- 异步收发的性能,是三者中最好的,每秒钟可以处理几十万条消息。
问题:
同步收发消息的响应时延比较高,Kafka 不会立即发送出去,而是要“先攒一波再一起处理”,在它的 Broker 中,很多地方都会使用这种设计。因此当你的业务场景中,每秒钟消息数量没有那么多的时候,Kafka 的时延反而会比较高。所以,Kafka不太适合在线业务场景。
4、选择建议
- RabbitMQ:消息队列不是系统的主角之一,对功能和性能都没有很高的要求,开箱即用易于维护;
- RocketMQ:场景是处理在线业务,比如在交易系统中用消息队列传递订单,低延迟和金融级的稳定性;
- Kafka:海量的消息,收集日志、监控信息或是前端的埋点这类数据,或大量使用了大数据、流计算相关的开源产品。
03 | 消息模型:主题和队列有什么区别?
没有通用的标准
主题和队列有什么区别?
早期的消息模型:队列模型。
弊端:
多个消费者之间是竞争关系,每个消费者只能收到队列中的一部分消息,任何一条消息只能被其中的一个消费者收到。
发布 - 订阅模型(Publish-Subscribe Pattern)
- 发布者(Publisher):消息的发送方
- 订阅者(Subscriber):消息的接收方
- 主题(Topic):服务端存放消息的容器
发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。
两种模型的区别:一份消息数据能不能被消费多次的问题。
RabbitMQ 的消息模型
使用队列模型
针对队列模型问题的解决方案:
- Exchange 位于生产者和队列之间,生产者将消息发送给 Exchange,由 Exchange 上配置的策略来决定将消息投递到哪些队列中。
- 每个队列中都存放一份完整的消息数据。
RocketMQ 的消息模型
标准的发布 - 订阅模型
消息队列的消费机制
“请求 - 确认”机制,确保消息不会丢失。
- 生产者先将消息发给服务端,服务端收到消息并将消息写入主题或者队列中后,会给生产者发送确认的响应。
- 生产者没收到确认或者收到失败的响应,则会重新发送消息;
- 在消费端,消费者在收到消息并完成自己的消费业务逻辑(比如,将数据保存到数据库中)后,也会给服务端发送消费成功的确认;
- 服务端只有收到消费确认后,才认为一条消息被成功消费,否则它会重新发送消息,直到收到对应的消费成功确认。
问题:
在某一条消息被成功消费之前,下一条消息是不能被消费的,否则会出现消息空洞,违背了有序性这个原则。
意味着每个主题在任意时刻,至多只能有一个消费者实例在进行消费,没法通过水平扩展消费者的数量来提升消费端总体的消费性能。
RocketMQ的解决方式:
主题下面增加了队列的概念。
每个主题包含多个队列,通过多个队列来实现多实例并行生产和消费。
注意:RocketMQ 只在队列上保证消息的有序性,主题层面是无法保证消息的严格顺序的。
- 订阅者:通过消费组来体现,一个消费组是一个订阅者;
- 消费组:每个消费组都消费主题中一份完整的消息,不同消费组之间消费进度彼此不受影响,一条消息被消费组1 消费过,也会再给 消费组2 消费。
- 消费者:消费组中包含多个消费者,同一个组内的消费者是竞争消费的关系,每个消费者负责消费组内的一部分消息。如果一条消息被消费者 消费了,那同组的其他消费者就不会再收到这条消息。
- 主题(Topic):由于消息需要被不同的组进行多次消费,所以消费完的消息并不会立即被删除,这就需要 RocketMQ 为每个消费组在每个队列上维护一个消费位置(Consumer Offset),这个位置**之前的消息都被消费过,之后的消息都没有被消费过,**每成功消费一条消息,消费位置就加一。
- 注意:丢消息的原因大多是由于消费位置处理不当导致的。
Kafka 的消息模型
Kafka 的消息模型和 RocketMQ 是完全一样的;
唯一的区别是,在 Kafka 中,队列对应的名称是“分区(Partition)”,含义和功能没有区别。