概述
通过队列功能进行消息传递,生产者将消息放到队列里,消费者可以到指定的队列去拉取消息,或者订阅对应的队列,由MQ服务端给其推送消息
核心功能
1,流量削峰填谷
在当前互联网的业务场景下,比如商品秒杀,在较短时间内,瞬时涌入大量请求,这个时候系统资源可能会耗尽,造成服务器瘫痪。 可以使用消息队列来缓冲瞬时流量,通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去 这个过程中由于消息队列限制了消息的发送频率,所以肯定会有大量的消息积压在消息队列中可能会产生消息积压的问题,这个在实际情况中需要考虑
2,应用解耦
常用在微服务系统中,用于提高系统的可用性,比如在一个商城系统中,有订单服务和物流服务,从订单服务产生的请求通过消息队列发送给物流服务,如果物流服务出现了问题,消息会短时间内积压在消息队列中,直到下游服务恢复之后,会继续处理请求,在这个过程中用户是感知不到的
3,异步处理
可以类比多线程优化接口的响应性能,A 调用 B 服务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此消息转发给 A 服务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供回调接口。同样B 服务也不用做这些操作。A 服务还能及时的得到异步处理成功的消息,比如一些文件上传的服务
AMQP
在rabbitmq中主要使用的是AMQP协议, 特性:
- 独立于平台的底层消息传递协议。
- 消费者驱动消息传递。
- 跨语言和平台的互用性、属于底层协议。
- 有5种交换类型direct,fanout,topic,headers,system。
- 面向缓存的、可实现高性能、支持经典的消息队列,循环,存储和转发。
- 支持长周期消息传递、支持事务(跨消息队列)
RabbitMQ的核心概念:
Producer(生产者)、Connection(连接)、Channel(信道)、Exchange(交换机)、Queue(队列)、Virtual host(虚拟主机)、Consumer(消费者)、Routing Key(路由键)、Binding(绑定)等
解释:
Producer(生产者)
产生数据发送消息的程序是生产者(Producer)
。
Connection(连接)
每个生产者或者消费者要通过RabbitMQ
发送与消费消息,首先就要与RabbitMQ
建立连接,这个连接就是Connection
,这是一个TCP
长连接。
Channel(信道)
如果每一次访问RabbitMQ
都建立一个Connection
,在消息量大的时候建立 TCP 连接
的开销将是巨大的,效率也较低,在系统访问流量高峰时,会严重影响系统性能。
Channel(信道)
是在Connection
内部建立的逻辑连接,如果应用程序支持多线程,通常每个线程创建单独的channel
进行通讯, channel 之间是完全隔离的。Channel
作为轻量级的Connection
极大减少了操作系统建立TCP connection
的开销 。
RabbitMQ
中大部分的操作都是使用Channel
完成的,比如:声明Queue
、声明Exchange
、发布消息、消费消息等。 这个可以类比用netty实现websocket的过程中,netty中的channel
Broker(服务端)
Broker(服务端)
接收和分发消息的应用,RabbitMQ Server
(服务端) 就是 Message Broker
。
Virtual host(虚拟主机)
Virtual host
是一个虚拟主机的概念,一个Broker
中可以有多个虚拟主机。
出于多租户和安全因素设计的,把 AMQP
的基本组件划分到一个虚拟的分组中,类似于网络中的命名空间概念。
当多个不同的用户使用同一个RabbitMQ server
提供的服务时,可以划分出多个vhost
,每个用户在自己的 vhost
创建 exchange/queue
等,很好地做到了不同用户之间相互隔离的效果。
Exchange(交换机)
交换机
是 RabbitMQ
非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推送到多个队列,亦或者是把消息丢弃,这个由交换机类型决定。
交换机有4种类型:direct、fanout、topic、headers
。
Queue(队列)
队列是RabbitMQ
内部使用的一种数据结构,尽管消息流经RabbitMQ
和应用程序,但它们只能存储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。
Binding(绑定)
Binding(绑定)
是指交换机和队列之间的虚拟连接,绑定信息被保存到交换机中的查询表中,用于消息的分发依据。
Routing Key(路由键)
创建好Exchange
和Queue
之后,需要使用Routing key(或者叫做Binding key)
将它们绑定起来,生产者在向交换机发送一条消息的时候,必须指定一个Routing key
,然后交换机接收到这条消息之后,会解析Routing key
,然后根据Exchange
和Queue
的绑定规则,将消息分发到符合规则的Queue
中。
Consumer(消费者)
接受消息并进行消费的程序是Consumer(消费者)

工作模式
由于我的简历中使用消息队列的时候主要使用的是发布/订阅模式和点对点模式,所以这里先着重分析这两种工作模式
1,发布/订阅模式
作用流程:消息的发布者通过消息通道将消息广播出去,让订阅该消息主题的订阅者消费
流程如下: 1、 生产者将消息发送到交换机;
2、 交换机将信息发给所有绑定的队列;
3、 绑定队列的消费者收到消息;
该模式需要指定一个Exchange
交换机,起本身只负责转发消息,不具备存储消息的能力。一方面,接收生产者发送的消息。另一方面,需要知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。
到底如何操作,取决于Exchange
的类型。有常见以下几种类型:
- Fanout:广播。将消息交给所有绑定到交换机的队列
- Direct:定向。把消息交给符合指定routing key的队列
- Topic:通配符。把消息交给符合routing pattern(路由模式)的队列
- Header:通过消息内容中的headers属性来进行匹配
2,点对点工作模式
其中一个消息只能被一个消费者接收。这种模式通常用于任务队列(Task Queue),确保每个任务只被一个工作进程(Worker)处理
流程如下:
- 生产者(Producer) :
- 生产者将消息发送到队列中。
- 消息被发送到一个特定的队列,而不是多个队列。
- 队列(Queue) :
- 队列存储消息,直到它们被消费者消费。
- 每个消息在队列中只能被一个消费者接收。
- 消费者(Consumer) :
- 一个或多个消费者从队列中接收消息。
- 每个消息只能被一个消费者接收和处理。
- 消费者处理完消息后,通常会发送一个确认(Acknowledgment)回队列,表示消息已被成功处理。
这个过程记住,在消费完成之后,要发送一个确认消息回队列,并且如果有多个消费者的时候,消息队列会自动的进行负载均衡,这种主要适用于不被重复处理的任务中。
最常见八股
这里我先只结合我的面试和看的一些八股资料说一下最常见的八股,其它的我会在后面单独再写一篇,和netty一篇一样,这里的答案主要来自于小林codeing等八股资料
1,消息重复消费怎么办
生产端为了保证消息发送成功,可能会重复推送,知道收到成功ACK,会产生重复的消息,但是仅靠消费端是无法完全保证的,在高并发场景中,发送消息,接收消息的这个过程通常会在一个事务中,会产生重复消费,另外消费端服务宕机也可能会产生重复消息,所以在业务端,对于已经重复消费的消息,在本地数据库或者redis中要做好表示,保证幂等性。
在我的简历项目中,主要采用的是开启手动确认机制,和在Redis中添加消息ID用来确保幂等性
2,消息丢失怎么办
- 使用消息持久化机制(MessageDeliveryMode.PERSISTENT),确保消息在RabbitMQ服务器重启后不会丢失;
- 在生产者端使用消息确认机制(publisher confirm),确保消息成功发送到RabbitMQ服务器
- 在消费者端使用手动确认模式(manual ack),确保消息被正确处理后才会被删除;
- 对于重要的业务消息,使用死信队列机制处理消费失败的消息,防止消息丢失。
在kafka中一般是采用集群部署,一个节点有多个副本来确保消息不会丢失
3,消息积压的问题怎么处理
消息积压是由于生产消息的速度大于消费者消费消息的速度。 首先可以先看看是不是业务中出现bug,或者消费者服务宕机了 其次可以优化消费逻辑,比如进行批量消息消费,或者进行水平扩容 最后如果积压时间很长,影响了系统的正常运行,可以紧急临时扩容