最近一段项目实践中大量使用了基于RabbitMQ的消息中间件,也积累的一些经验和思考,特此成文,望大家不吝赐教。
本文包括RabbitMQ基本概念、进阶概念、实践与思考等三部分,着重强调相关概念和基于RabbitMQ进行扩展开发的思路,并简要展示RabbitMQ客户端的编码,接下来通过一个思维导图来展示整体思路,红星表示重点部分。
官方文档: #getstarted
1.1.核心实体进入详细介绍之前,先来看一张简化版的消息流转的模型图。
a.生产者Producer发送消息,消息分为消息标识和消息体(payLoad有效载荷)两部分,消息标识中包含Exchange交换器的名字和RoutingKey路由键信息。
b.由于交换器之前已经通过2个不同的BindingKey绑定键分别绑定了两个队列,因此交换器可以对比路由键和绑定键,之后将消息路由到匹配的队列中。
c.队列将消息推送到指定的一个或多个消费者中,多个消费者会选择最简单的RoundRobin轮训方式进行选择。
Exchange交换器
核心概念,可以简化理解为路由器,其不存储数据,其通常会和一个队列绑定,但也可以绑定到另一个交换器上。其包括4种类型的交换器类型,生产实践中主要使用可以精细管理的direct和topic两种。direct,路由规则为完全匹配;topic,支持完全匹配,也支持模糊匹配;fanout,会将消息转发到该交换器绑定的所有队列中;header,实际中无应用。
Queue队列
用于存储消息,和Kafka的消息模型完全不同,其会将消息存储在Topic中。因此在实现类似ConsumerGroup概念时差异很大,Kafka是可以回溯消息的,但Rabbit新绑定的队列的数据是空的,不能回溯。
Binding绑定
其通过绑定键将交换器和队列关联起来
RouteKey & BindingKey 路由键和绑定键
通常会将路由键和绑定键都称为路由键,其差异是路由键是包含在消息标识中的,而绑定键是用于在交换器和队列间建立绑定关系的,消息会通过它们的匹配情况进行路由。
1.2.通信通信实体
包括Connection连接和Channel通道,连接通常对应一个基于TCP的Socket,建立Connection的关键参数包括用户名、密码、虚拟主机、主机地址和端口。一个连接可以建立多个Channel实例,推荐控制数量(比如10个),但Channel实例不能在线程间共享,应用程序需要为每一个线程开辟一个Channel。
AMQP协议
Java技术栈汇中,关于消息通信听到比较多的是JMS,而AMQP协议相对更加严格一些,其包括Module Layer,Session Layer, Transport Layer三个层次,业务开发主要接触到的是Module Layer,客户端可以通过Queue.Declare、Basic.Consume等命令进行操作。
1.3.虚拟主机与用户vhost
虚拟主机,可以在逻辑上看做一台RabbitMQ服务器,其拥有自己的交换器、队列和绑定关系等。RabbitMQ对权限的管理就是基于vhost进行的,默认会创建一个全局的/虚拟主机,通常不推荐直接使用该vhost,而是需要自定义一个vhost便于管理。
User
对于某一个用户,通常包括3种类型的权限:read,允许读取队列数据;write,允许向队列发送数据;config,允许创建队列,如果客户端需要支持添加队列,需要添加该权限,否则会报无权限错误【踩过坑】。
TTL过期时间
目前在两个不同的粒度设置消息的TTL,分别是队列粒度和消息粒度。由于RabbitMQ实际机制的原因,通常都选择的是队列粒度,对于队列粒度来说,队列头的消息一定是最先失效的,因此可以高效的判断和丢弃。而对于消息粒度,其需要在消息真正投递到消费者时进行判断,如果该消息之前的消息并没有失效,那么它将一直存活。
死信交换器DLX
全称为 Dead-Letter-Exchange,也是RabbitMQ扩展开发的核心概念,当一条消息在一个队列中变成死信之后,它能自动的被转发到一个交换器中,这个交换器就是DLX,很多地方称和这个交换器绑定的队列是死信队列, 我并不是完全认同。
消息变为死信的原因