文章目录
- 为什么要使用消息队列呢?
- 如何用RocketMQ做削峰填谷的?
- [为什么要选择 RocketMQ?](#为什么要选择 RocketMQ?)
- 消息队列有哪些消息模型?
- 消息的消费模式了解吗?
- [RocketMQ 的基本架构了解吗?](#RocketMQ 的基本架构了解吗?)
- 详细介绍一下RocketMQ的NameServer吗?
- 请说说Broker的作用?
- 生产者?
- 消费者?
- 如何保证消息的可用性/可靠性/不丢失呢?
- 顺序消息如何实现?
- 延时消息了解吗?
- 死信队列知道吗?
- [如何保证 RocketMQ 的高可用?](#如何保证 RocketMQ 的高可用?)
- [说一下 RocketMQ 的整体工作流程?](#说一下 RocketMQ 的整体工作流程?)
为什么要使用消息队列呢?
我认为消息队列的核心价值主要体现在四个方面。首先是解耦,这是最重要的。
模块会有很多后续的任务,处理不仅数据量大,而且任务本身也比较消耗资源,没有消息队列的话,就得等这些任务都处理完才能返回结果给用户,体验会很差。
异步处理,系统可以将那些耗时的任务放在消息队列中异步处理,从而快速响应用户的请求。比如说,用户下单后,系统可以先返回一个下单成功的消息,然后将订单信息放入消息队列中,后台系统再去处理订单信息。
就是削峰填谷,这一点在高并发场景下特别重要。比如秒杀活动,瞬间可能来了几十万个请求。如果直接打到数据库,系统肯定会崩溃。但通过消息队列,所有请求先进队列,后端消费者按照自己的处理能力逐个消费,即使暂时处理不过来,消息也能安全地存储在队列里。这样系统就不会被突发流量打倒。
消息队列还支持持久化存储,支持消息重试和事务机制。这样即使消费者在处理消息时出现异常,消息也不会丢失,可以重新投递处理,最终保证业务逻辑一定会被正确执行
如何用RocketMQ做削峰填谷的?
用户的所有请求不直接打到后端服务,而是先发送到 RocketMQ 的消息队列里。RocketMQ 作为一个高吞吐量的中间件,能够快速接收这些请求。然后消费者端根据自己的处理能力,按照一定的速度从队列里拉取消息进行处理。这样就形成了一个缓冲区,能够吸收掉突发的流量。
就拿秒杀场景来举例吧。首先,用户的秒杀请求不是直接去扣减库存,而是先发一条消息到 RocketMQ。这个操作很快,因为只是把消息丢到队列里,不涉及任何业务逻辑处理。然后在消费端,我们启动一个消费者线程,这些消费者以一个相对稳定的速度去消费消息,一条一条地处理秒杀逻辑,比如检查库存、扣库存、生成订单等。
这里有一个需要注意的地方,就是消息堆积的问题,如果消费者一直跟不上生产速度,消息会无限堆积,可能最终会导致磁盘满或者消息过期被删除。所以在实际项目中,我们需要监控队列的堆积情况,必要时通过增加消费者或优化消费逻辑来加快处理速度
为什么要选择 RocketMQ?
事务:在事务支持方面,RocketMQ 做得特别好。比如说在转账场景下,我们要保证"扣款"的本地事务和"发送转账消息"这两个操作要么都成功,要么都失败,不能出现只扣款但没发消息的情况。RocketMQ 的事务消息机制就能很好地解决这个问题。
顺序消息的支持。在很多场景下,消息的顺序很重要。比如订单的生命周期,应该是:下单 → 支付 → 发货 → 确认收货。如果消息乱序了,就会出现还没付款就发货的逻辑混乱。
RocketMQ 的顺序消息能够保证同一个 OrderId 的消息,它们能够被发送到同一个队列,然后被同一个消费者按顺序消费。
高可用部署:支持 Master-Slave 模式的高可用部署。当 Master 节点宕机时,Slave 可以自动转换为 Master,从而提供更好的容错能力。
如果是日志收集和流式处理场景,Kafka 更合适,因为它天生为大数据场景设计。
如果是需要轻量级的消息传递,RabbitMQ 更好,因为它实现了 AMQP 协议,支持丰富的路由和交换机类型。
消息队列有哪些消息模型?
对点模型和发布-订阅模型。
点对点模型:一条消息只能被一个消费者消费。生产者把消息发送到一个队列里,消费者从这个队列里拉取消息进行处理。一旦消息被某个消费者消费了,这条消息就被删除了,其他消费者是看不到这条消息的。
发布-订阅模型:一条消息可以被多个订阅者消费。生产者发布消息到一个主题(Topic),所有订阅了这个主题的消费者都会收到这条消息。
RocketMQ 的消息模型
RocketMQ 采用的是一个统一的、基于 Topic 和 Group 的消息模型。同一个消费者组内可以算是点对点,不同消费者组之间算是发布-订阅。
在 RocketMQ 中,主题(Topic)是消息的逻辑分类。生产者把消息发送到某个 Topic,消费者从某个 Topic 拉取消息。一个 Topic 可以有多个生产者向它发送消息,也可以有多个消费者从它消费消息。
一个 Topic 在物理上被分成了多个队列(Queue)。生产者发送消息时,消息会根据某个 key 被路由到不同的 Queue 中。这个设计的巧妙之处在于,它既保证了单个 Queue 内的消息顺序,又能通过多个 Queue 实现并行处理。
消费者端,消费者归属于某一个消费者组(Consumer Group)。一个 消费者组内的多个消费者会协同消费同一个主题的消息。RocketMQ 会把主题下的多个队列分配给这个消费者组内的消费者进行消费。
消息的消费模式了解吗?
两个维度来分类:一个是消费的方向,一个是消费的范围。
从消费方向来分的话, 一种是 pull 模式,一种是 push 模式。
pull 模式需要消费者主动去消息队列中拉取消息,消费者可以控制拉取的速度、数量,但需要不断地轮询,比较浪费资源。
push 模式则是消息队列主动把消息推送给消费者,消费者只需要注册一个监听器,消息一到达就触发回调进行处理,响应速度快,但可能会出现消息堆积的情况。
从消费范围来分的话,一种是集群消费,一种是广播消费。
集群消费是指,同一个消费者组中的多个消费者共同消费一个主题中的消息。消息被分散分配给这个消费者组中的各个消费者,每条消息只被这个消费者组中的一个消费者消费。
换句话说,RocketMQ 会把主题下的所有队列均匀地分配给消费者组内的消费者,实现负载均衡。这样也能保证同一条消息只会被消费者组内的一个消费者消费,避免重复消费。
广播消费是指,同一个主题的每条消息都会被消费者组内的每个消费者消费一次。也就是说,消费者组内的每个消费者都会收到主题下的所有消息,从而实现消息的广播效果。
RocketMQ 的基本架构了解吗?
NameServer、Broker、生产者和消费者。
NameServer是RecketMQ的路由中心,负责维护Topic和Broker之间的路由信息,生产者和消费者都会从NameServer获取最新的路由信息
- 每个 Broker 会向 NameServer 注册自己的信息,包括 Broker 的地址、端口、存储的 Topic 和 Queue 等。
- NameServer 会根据 Topic 名称告诉生产者和消费者对应的 Broker 地址。
- Broker 会定期向 NameServer 发送心跳,报告自己的状态。
Broker 是消息存储中心,它的职责包括:
- 所有生产者发送的消息都会被存储在 Broker 上,以文件的形式持久化到磁盘。
- 消费者从 Broker 拉取消息时,Broker 需要根据消费者的 Offset 找到对应的消息,返回给消费者。
- 如果配置了高可用,Broker Master 会把消息同步到 Broker Slave,实现主从备份。
生产者在发送消息时,会先从 NameServer 获取 Topic 的路由信息,然后根据路由信息把消息发送到对应的 Broker 上。
消费者在消费消息时,也会先从 NameServer 获取 Topic 的路由信息,然后根据路由信息从对应的 Broker 上拉取消息进行处理。
详细介绍一下RocketMQ的NameServer吗?
NameServer 是一个路由中心和服务发现中心。他的第一个职责是存储和维护路由信息。当 Broker 启动时,会向 NameServer 注册自己的信息,NameServer 把这些信息存储在内存里,形成一个路由表
它的第二个职责是提供路由查询服务。当生产者或消费者需要知道某个主题在哪个 Broker 上时,就向 NameServer 查询。NameServer 会根据主题名称返回对应的 Broker 地址和队列信息。
第三个职责是监控 Broker 的状态。Broker 会定期向 NameServer 发送心跳,报告自己的状态。如果某个 Broker 长时间没有发送心跳,NameServer 会将其标记为不可用,并从路由表中移除。
请说说Broker的作用?
Broker 是一个消息存储服务器,它负责接收生产者的消息,并将其存储起来,然后在消费者拉取时返回给它们。
Broker 是消息存储中心,它的职责包括:
- 所有生产者发送的消息都会被存储在 Broker 上,以文件的形式持久化到磁盘。
- 消费者从 Broker 拉取消息时,Broker 需要根据消费者的 Offset 找到对应的消息,返回给消费者。
- 如果配置了高可用,Broker Master 会把消息同步到 Broker Slave,实现主从备份。
生产者在发送消息时,会先从 NameServer 获取 Topic 的路由信息,然后根据路由信息把消息发送到对应的 Broker 上。
消费者在消费消息时,也会先从 NameServer 获取 Topic 的路由信息,然后根据路由信息从对应的 Broker 上拉取消息进行处理。
生产者?
生产者的核心职责是把应用程序的数据转化为消息,发送到 Broker。
RocketMQ 提供了三种方式发送消息:同步、异步和单向。
同步发送:生产者发送消息后会阻塞等待 Broker 的响应。只有收到 Broker 确认消息已存储的响应后,才会返回给应用程序。
异步发送:生产者发送消息后立即返回,不阻塞。Broker 的响应会通过回调函数返回给应用程序。
单向发送:生产者发送消息后直接返回,不等待响应,也不需要回调。这个模式用于一些不关心发送结果的场景。
消费者?
消息的接收方,它的核心职责就是从 Broker 拉取消息,进行业务处理,然后提交消费位移。
支持 Pull、Push、Pop 三种消费模型。
Pull 模型是最基础的消费方式。消费者主动向 Broker 发起请求,拉取消息。Push 模型在使用上看起来像是服务端在推送消息,但实际上底层仍然是 Pull 模型。
当消费者很多的时候,消费重平衡会消耗很长的时间,于是 RocketMQ 提供了 Pop 模型。
Pop 模型把消费重平衡完全移到了服务端,以减轻消费者的负担。
如何保证消息的可用性/可靠性/不丢失呢?
从生产阶段、存储阶段、消费阶段来看
在生产阶段,主要通过请求确认机制,来保证消息的可靠传递。
存储阶段,可以通过配置可靠性优先的 Broker 参数来避免因为宕机丢消息,简单说就是可靠性优先的场景都应该使用同步。
消息 成功消费的关键在于确认的时机,不要在收到消息后就立即发送消费确认,而是应该在执行完所有消费业务逻辑之后,再发送消费确认。因为消息队列维护了消费的位置,逻辑执行失败了,没有确认,再去队列拉取消息,就还是之前的一条。
顺序消息如何实现?
全局顺序是指整个 Topic 的所有消息都严格按照发送顺序消费,这种方式性能比较低,实际项目中用得不多。
局部顺序是指特定分区内的消息保证顺序,这是我们常用的方式。
保证顺序,关键是要把需要保证顺序的消息发送到同一个 MessageQueue 中
java
// 根据订单ID选择队列,保证同一订单的消息在同一队列
producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
String orderId = (String) arg;
int index = orderId.hashCode() % mqs.size();
return mqs.get(index);
}
}, orderId);
每个 MessageQueue 在 Broker 中对应一个 ConsumeQueue,消息按照到达 Broker 的顺序依次写入。
当消费者开始消费某个 MessageQueue 时,会在 Broker 端对该队列加锁,其他消费者就无法同时消费这个队列。这样确保了同一时间只有一个消费者在处理某个队列的消息,从而保证了消费顺序。
延时消息了解吗?
比如订单超时自动取消,就是一个典型的利用延时消息的例子,用户提交了一个订单,就可以发送一个延时消息,1h 后去检查这个订单的状态,如果还是未付款就取消订单释放库存
临时存储+定时任务
只需要在生产消息的时候设置消息的延时级别:
java
// 实例化一个生产者来产生延时消息
DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup");
// 启动生产者
producer.start();
int totalMessagesToSend = 100;
for (int i = 0; i < totalMessagesToSend; i++) {
Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes());
// 设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)
message.setDelayTimeLevel(3);
// 发送消息
producer.send(message);
}
临时存储+定时任务
Broker 收到延时消息了,会先发送到主题(SCHEDULE_TOPIC_XXXX)的相应时间段的 Message Queue 中,然后通过一个定时任务轮询这些队列,到期后,把消息投递到目标 Topic 的队列中,然后消费者就可以正常消费这些消息
死信队列知道吗?
列用于存储那些无法被正常处理的消息,这些消息被称为死信(Dead Letter)
产生死信的原因是,消费者在处理消息时发生异常,且达到了最大重试次数。当消费失败的原因排查并解决后,可以重发这些死信消息,让消费者重新消费;如果暂时无法处理,为避免到期后死信消息被删除,可以先将死信消息导出并进行保存。
如何保证 RocketMQ 的高可用?
NameServer 因为是无状态,且不相互通信的,所以只要集群部署就可以保证高可用。
体现在 Broker 的读和写的高可用,Broker 的高可用是通过集群和主从实现的。
Broker 可以配置两种角色:Master 和 Slave,Master 角色的 Broker 支持读和写,Slave 角色的 Broker 只支持读,Master 会向 Slave 同步消息。
也就是说 Producer 只能向 Master 角色的 Broker 写入消息,Cosumer 可以从 Master 和 Slave 角色的 Broker 读取消息。
Consumer 的配置文件中,并不需要设置是从 Master 读还是从 Slave 读,当 Master 不可用或者繁忙的时候, Consumer 的读请求会被自动切换到从 Slave。有了自动切换 Consumer 这种机制,当一个 Master 角色的机器出现故障后,Consumer 仍然可以从 Slave 读取消息,不影响 Consumer 读取消息,这就实现了读的高可用。
如何达到发送端写的高可用性呢?在创建 Topic 的时候,把 Topic 的多个 Message Queue 创建在多个 Broker 组上(相同 Broker 名称,不同 brokerId 机器组成 Broker 组),这样当 Broker 组的 Master 不可用后,其他组 Master 仍然可用, Producer 仍然可以发送消息 RocketMQ 目前还不支持把 Slave 自动转成 Master ,如果机器资源不足,需要把 Slave 转成 Master ,则要手动停止 Slave 色的 Broker ,更改配置文件,用新的配置文件启动 Broker。
说一下 RocketMQ 的整体工作流程?
RocketMQ 是一个分布式消息队列,也就是消息队列+分布式系统
作为消息队列,它是发-存-收的一个模型,对应的就是 Producer、Broker、Cosumer;
作为分布式系统,它要有服务端、客户端、注册中心,对应的就是 Broker、Producer/Consumer、NameServer
所以我们看一下它主要的工作流程:RocketMQ 由 NameServer 注册中心集群、Producer 生产者集群、Consumer 消费者集群和若干 Broker(RocketMQ 进程)组成:
Broker 在启动的时候去向所有的 NameServer 注册,并保持长连接,每 30s 发送一次心跳
Producer 在发送消息的时候从 NameServer 获取 Broker 服务器地址,根据负载均衡算法选择一台服务器来发送消息
Conusmer 消费消息的时候同样从 NameServer 获取 Broker 地址,然后主动拉取消息来消费