RabbitMQ 入门知识
我们在项目里用到了 MQ,但是之前我们发现了很多 MQ 的问题:比如消费者挂了,消费内容堆积,消费者重复循环死亡等情况,之前我对于 MQ 一无所知,这些问题都无法回答——我们的代码看上去很标准,也很简单,似乎没有什么问题。但是实际上从开发时就是由一群对 MQ 没有概念的人进行的,因此才出现了这么多问题。
公司的 MQ 是单独定制的,基于 AMQP 0-9-1 去开发的,而实际在使用过程中,其实就是一个 RabbitMQ 的封装版,因此还是以 Rabbit MQ 为主来讲下学习的内容。
AMQP 0-9-1?
AMQP(高级消息队列协议)是一个网络协议,它支持符合要求的客户端应用(application)和消息中间件代理(messaging middleware broker)之间进行通信。
简单的来说,可以看做一个 MQ 规范,而 RabbitMQ 是基于这一规范来实现的,实际上,它定义了基础概念和一些必要的接口的实现必要条件(可以看作是抽象类)。关于 RabbitMQ 中的所有概念,你都能在 AMQP 中找到对应的介绍。
所以,一些基础概念
RabbitMQ 中除了正常的生产者消费者以外,还有一些其他的概念来支撑这样一个复杂的消息队列。
Broker
Broker 是消息服务中间件中的一个服务节点,大部分情况下可以把 一个 Broker 看成一个 RabbitMQ 的服务器——
从这张图中可以看出 Broker 相当于一个消息服务的中央节点,而我们的消息队列核心功能也就在 Broker 上
队列
消息都存储在队列中,下图是一个简单的模型,实际上,我们也可以手写一个消息队列服务,只要有生产者、消费者和存储单元组成队列就行了:
Exchange
交换机(Exchange)在 RabbitMQ 中是一个非常重要的概念,承担了一些队列的逻辑处理功能——默认来说,对于生产者,我只知道把产生的内容丢到 MQ 当中,但是发到哪个队列中,这一点对生产者来说是无感知的,也不知道目前队列的状况如何,而 Exchange 就承担了「发到哪个队列中」的职责,用几种路由策略来决定如果分发给不同的队列。
Connection 和 Channel
每一个 Connection 是一条 TCP 连接,理论上而言每一个生产者和消费者都需要一条 Connection,但是 TCP 连接的开销会很大,因此我们会使用 Channel 来进行 TCP 复用,减少性能的开销。
Exchange 类型
熟悉了一些基本概念之后,我们大致知道了 RabbitMQ 的运作流程和连接时的一些参数,接下来开始介绍 Exchange 的类型,帮助我们更好地理解 Exchange 这个概念。
fanout
我们比较常用的一种 Exchange 类型,它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。
direct
把消息路由到 binding key 和 routing key 完全匹配的队列中。
binding key 和 routing key 基本上可以理解为一个对 Queue 的称呼。
图中 Queue1 叫 warning
,Queue2 可以叫 info
,warning
或者 debug
,那样 Exchange 叫了声 warning
的时候就会有两个 Queue 过来,拿到数据,而 info
则只有 Queue2 会回应。
(图来自:《RabbitMQ 实战指南》)
topic
direct 类型太过严格,大部分时间我们都用不上这么严格的规则,因此有了 topic。
topic 可以看做是一种正则表达式规则,满足正则表达式的规则就会进入队列。
headers
这种类型根据发送消息中的 header 来匹配,性能差,基本没用。
死信队列
介绍完了基础概念,接下来就要说一些常用的知识了,第一个就是死信队列:
如果开启死信队列,那么当以下问题产生的时候:
- 消息被拒绝(basic.reject/ basic.nack)并且不再重新投递 requeue=false
- 消息超期 (rabbitmq Time-To-Live -> messageProperties.setExpiration())
- 队列超载
导致队列无法处理的时候,有一个兜底策略,使得消息不会被堆积在队列里,而换到死信队列被消费。
在 RabbitMQ 中开启死信队列非常简单,只要配置为 DLX 即可。
共用 Connection 而不是 Channel
共用 Connection 的理由在上文已经说过了,那么为什么我们不建议共用 Channel 呢?
实际上大家都知道,计算机网络传输信息的时候,本质上都是二进制传输,而传输的数据经过一定的处理,最终变成了我们可读可处理的数据,Channel 已经是复用了 TCP 连接的,此时如果我们再进行并行的数据传输,很有可能会导致某一帧数据的异常。在使用的过程中,由于我们的程序复用了 Channel 同时提供给生产者和消费者,也确实遇到了这样的问题。
总结吐槽
这篇文章又拖了一个月,很多要写的都忘了……随便喷吧
参考资料
- rabbitmq教程
- RabbitMQ 中文文档
- 《RabbitMQ 实战指南》
植入部分
如果您觉得文章不错,可以通过赞助支持我。
如果您不希望打赏,也可以通过关闭广告屏蔽插件的形式帮助网站运作。
补充个点,MQ常规用法都是有去无回的,所以有些业务场景需要反馈结果的时候就需要另外起个回调队列,我们之前的做法是在生产者发消息前 自己作为消费者开始监听一个唯一GUID队列,然后把队列名连带消息发送给另外一端的消费者,消费者收到消息之后有结果后通过这个GUID发回来。你们公司定制的那个MQ有内置处理这种反馈结果的么?
没有,我们这类并没有需要回调的场景,所以这部分没有研究过
RabbitMQ中提供这种回调队列实现RPC效果的方案,示例部分:https://www.rabbitmq.com/tutorials/tutorial-six-python.html
但从实际场景上,目前未接触过此类应用场景,有不错的场景可以分享下
有很多例子的,比如说用MQ做分布式事务的消息通道,那么不同的业务节点之间的通信就需要等待结果才能往下运行,这类场景一般没办法异步去获取结果,所以需要回调。