10 RabbitMQ的架构设计与实现
系统架构
RabbitMQ 由 Producer、Broker、Consumer 三个大模块组成。生产者将数据发送到 Broker,Broker 接收到数据后,将数据存储到对应的 Queue 里面,消费者从不同的 Queue 消费数据。
协议和网络模块
主要包含 tcp_listener、tcp_acceptor、rabbit_reader 三个进程。如下图所示,RabbitMQ 服务端通过 tcp_listener 监听端口,tcp_acceptor 接收请求,rabbit_reader 处理和返回请求。本质上来看是也是一个多线程的网络模型。
数据存储
RabbitMQ 的存储模块也包含元数据存储与消息数据存储两部分。如下图所示,RabbitMQ 的两类数据都是存储在 Broker 节点上的,不会依赖第三方存储引擎。我们先来看一下元数据存储。
队列索引负责存储、维护队列中落盘消息的信息,包括消息的存储位置、是否交付、是否 ACK 等等信息。队列索引是 Queue 维度的,每个 Queue 都有一个对应的队列索引。
生产者和消费者
当生产者和消费者连接到 Broker 进行生产消费的时候,是直接和 Broker 交互的,不需要客户端寻址。客户端连接 Broker 的方式,跟我们通过 HTTP 服务访问 Server 是一样的,都是直连的。部署架构如下图所示
生产端发送数据不是直接发送到 Queue,而是直接发送到 Exchange。即发送时需要指定 Exchange 和 route_key,服务端会根据这两个信息,将消息数据分发到具体的 Queue。因为 Exchange 和 route_key 都是一个逻辑概念,数据是直接发送到 Broker 的,然后在服务端根据路由绑定规则,将数据分发到不同的 Queue 中,所以在客户端是没有发送生产分区分配策略的逻辑。其实从某种程度来看,Exchagne 和 Route 的功能就是生产分区分配的过程,只是将这个逻辑从客户端移动到了服务端而已。
RabbitMQ 支持 Push(推)和 Pull(拉)两种模式,如果使用了 Push 模式,Broker 会不断地推送消息给消费者。不需要客户端主动来拉,只要服务端有消息就会将数据推给客户端。当然推送消息的个数会受到 channel.basicQos 的限制,不能无限推送,在消费端会设置一个缓冲区来缓冲这些消息。拉模式是指客户端不断地去服务端拉取消息,RabbitMQ 的拉模式只支持拉取单条消息。
HTTP 协议支持和管控操作
开启插件后,就可以通过 HTTP 接口实现生产、消费、集群的配置、资源的创建、删除等操作
11 RocketMQ架构设计与实现
RocketMQ 在功能、稳定性、性能层面都比 RabbitMQ 的表现更好。所以我们一起来看看为什么吧
RocketMQ 系统架构
RocketMQ 由 Producer、NameServer、Broker、Consumer 四大模块组成。其中,NameServer 是 RocketMQ 的元数据存储组件。另外,在 RocketMQ 5.0 后,还增加了 Proxy 模块,用来支持 gRPC 协议,并为后续的计算存储分离架构做准备
协议和网络模块
在协议方面,如下图所示,RocketMQ 5.0 之前支持自定义的 Remoting 协议,在 5.0 之后,增加了 gRPC 协议的支持。
这是在 Proxy 组件上完成了对 gRPC 协议的支持,用的是第 03 讲的代理(Proxy)模式。即 Broker 依旧只支持 Remoting 协议,如果需要支持 gRPC 协议,那么就需要单独部署 Proxy 组件。
在传输层协议方面,Remoting 和 gRPC 都是基于 TCP 协议传输的。Remoting 直接基于四层的 TCP 协议通信,gRPC 是基于七层的 HTTP2 协议通信,不过 HTTP2 底层也是基于 TCP,它们本质上都是应用层的协议
gRPC架构
gRPC 分为 Client 端和 Server 端,底层基于 HTTP2 通信,内置了编解码模块,也定义好了 Client 和 Server 之间的调用方式,同时支持 TLS 加密,是一个完整的 RPC 框架。所以我们可以看到它在底层已经实现了网络通信、协议的设计、编解码框架等所有基础的工作。从使用的角度来说,gRPC 提供了各个语言的开发库,只需要集成对应语言的开发库,即可完成网络模块的开发,很轻便
数据存储
在底层的文件存储方面,并不是一个 MessageQueue 对应一个文件存储的,而是一个节点对应一个总的存储文件,单个 Broker 节点下所有的队列共用一个日志数据文件(CommitLog)来存储,和 RabbitMQ 采用的是同一种存储结构。存储结构如下图所示:
RocketMQ 不是按照主题或队列维度来清理数据的,而是按照节点的维度来清理的。原因和 RocketMQ 的存储模型有关,上面说到 RocketMQ 所有 Queue 的日志都存储在一个文件中,如果要支持主题和队列单独管理,需要进行数据的合并、索引的重建,实现难度相对复杂
生产者和消费者
RocketMQ 的客户端连接服务端是需要经过客户端寻址的。如下图所示,首先和 NameServer 完成寻址,拿到 Topic/MessageQueue 和 Broker 的对应关系后,接下来才会和 Broker 进行交互。
消息粒度负载均衡就是我们之前在第 08 讲讲到的共享消费模式,而队列粒度负载均衡就是独占消费模式。大部分情况下,我推荐你优先使用队列粒度负载均衡
Http协议的支持和管控
RocketMQ 的管控也是不支持 HTTP 协议的操作的。RocketMQ 的管控操作都是通过 Remoting 协议支持的,在 gRPC 协议中也不支持管控操作。即在 Broker 中,通过 Remoting 协议暴露不同的接口或者在 NameServer 中暴露 TCP 的接口,来实现一些对应的管控操作。
客户端 SDK 会集成调用服务端这些接口的逻辑。命令行工具就是通过客户端 SDK 来完成管控操作,也可以在代码中通过 SDK 来执行管控操作
12 Kafka的架构设计与实现
Kafka的系统架构
Kafka 由 Producer、Broker、ZooKeeper、Consumer 四个模块组成。其中,ZooKeeper 用来存储元数据信息,集群中所有元数据都持久化存储在 ZooKeeper 当中。
从消息的生命周期来看,生产者也需要通过客户端寻址拿到元数据信息。客户端通过生产分区分配机制,选择消息发送到哪个分区,然后根据元数据信息拿到分区 Leader 所在的节点,最后将数据发送到 Broker。Broker 收到消息并持久化存储。消费端使用消费分组或直连分区的机制去消费数据,如果使用消费分组,就会经过消费者和分区的分配流程,消费到消息后,最后向服务端提交 Offset 记录消费进度,用来避免重复消费。
协议和网络模块
Kafka 服务端的网络层是基于 Java NIO 和 Reactor 来开发的,通过多级的线程调度来提高性能
数据存储
生产者和消费者
HTTP 协议支持和管控
Kafka 内核是不支持 HTTP 协议的,如果需要支持,则需要在 Broker 前面挂一层代理。如 Confluent 开源的 Kafka Rest
13 Pulsar的架构设计与实现
作为消息队列后起之秀的 Pulsar,因为其存算分离、多租户、多协议、丰富的产品特性、支持百万 Topic 等特点,逐渐为大家所熟知。从定位来看,Pulsar 希望同时满足消息和流的场景。从技术上来看,它当前主要对标的是 Kafka,解决 Kafka 在流场景中的一些技术缺陷,比如计算层弹性、超大分区支持等等。
系统架构
协议和网络层
数据存储
Pulsar 需要存储更多元数据,所以 Pulsar 对 ZooKeeper 造成的压力会更大。也正因为如此,Pulsar 才会支持可插拔的元数据存储框架,希望通过其他的存储引擎来缓解 ZooKeeper 的瓶颈
生产者和消费者
HTTP 协议支持和管控
在访问层面,Pulsar 的管控操作和生产消费数据流操作是分开支持的。即数据流走的是自定义协议通信,管控走的是 HTTP 协议形式的访问。从访问上就隔离了管控和数据流操作,在后续的权限管理、客户端访问等方面提供了很多便利。