目录
[3.1. 消费者管理模块](#3.1. 消费者管理模块)
[3.2. 通信通道管理模块](#3.2. 通信通道管理模块)
[3.3. 连接管理模块](#3.3. 连接管理模块)
一.项目模块划分
本项目构建一个基于AMQP协议的消息队列系统,由三个独立进程组成:
-
服务端:消息队列的核心服务进程,负责处理所有数据存储、路由和投递逻辑。
-
发布客户端:生产者进程,用于向服务端发送消息。
-
订阅客户端:消费者进程,用于从服务端接收并处理消息。
1.1.服务端模块
1. 数据管理模块
该模块负责管理服务端的核心数据实体,包括以下四个子模块:
-
交换机数据管理模块
负责交换机的增加、删除、修改操作,并提供数据持久化存储功能。
-
队列数据管理模块
负责队列的增加、删除、修改操作,并提供数据持久化存储功能。
-
绑定数据管理模块
负责绑定关系(交换机与队列之间的关联)的增加、删除、修改操作,并提供数据持久化存储功能。
-
消息数据管理模块
负责消息的增加、删除、修改操作,并提供数据持久化存储功能。
2. 虚拟机数据管理模块
该模块是对上述四个数据管理模块(交换机、队列、绑定、消息)的整合与管理。
在消息队列系统中,一个虚拟机通常作为一个逻辑单元,包含完整的交换机、队列、绑定及消息数据,该模块负责协调这些数据的一致性与整体生命周期。
3. 路由匹配模块
负责处理消息的路由逻辑:当新消息发布到交换机时,由该模块根据交换机的类型决定应将其投递到哪些队列。
-
直接交换机:根据路由键完全匹配投递到对应队列。
-
广播交换机:将消息投递到所有绑定到该交换机的队列。
-
主题交换机:根据路由键与绑定键的模式匹配规则进行投递,支持通配符匹配。
该模块的核心功能是执行匹配逻辑,确保消息按规则正确路由。
4. 消费者管理模块
消费者指订阅了某个队列消息的客户端。当队列中有新消息到达时,该模块负责将消息推送给已订阅该队列的消费者。
在实现中,该模块维护消费者与队列的订阅关系,并在消息可投递时通过对应的通信通道向消费者推送消息。
5. 通信通道管理模块
在同一个网络连接中,可以创建多个独立的通信通道,每个通道负责处理一类消息或一个会话。
当某个客户端需要关闭通信时,实际关闭的是对应的通道,而非整个连接。
关闭通道时,该模块负责清理该通道对应的订阅关系及相关资源。
6. 连接管理模块
负责管理与客户端的网络连接。每个连接可能包含多个通信通道,当连接关闭时,需要同时清理该连接下的所有通道及对应的订阅信息。
该模块维护连接与通道之间的对应关系,确保资源正确释放。
7. 服务端BrokerServer模块
该模块是对上述所有模块的整合,对外提供统一的消息队列服务。它负责接收客户端连接、协调各模块协作,并实现消息的接收、存储、路由和投递等完整流程。
1.2.客户端模块
1. 消费者管理模块
在订阅客户端中,当用户订阅某一队列的消息时,即创建一个对应的消费者。该模块负责维护消费者状态,并处理服务端推送过来的消息。
2. 通信通道管理模块
客户端中的通信通道与服务端的通道一一对应,负责在客户端与服务端之间传输数据。客户端通过通道向服务端发送请求,并接收服务端的响应或消息推送。
3. 连接管理模块
客户端通过一个网络连接与服务端通信,该连接可承载多个通道。该模块负责维护连接的建立、保持与关闭,并整合客户端所用资源,为用户提供透明的通信服务。
4. 客户端封装实现
基于上述三个模块,封装为两种客户端类型:
-
发布客户端
用户可通过该客户端向指定交换机发布消息。客户端将消息通过通信通道发送至服务端,由服务端进行后续路由与投递。
-
订阅客户端
用户可通过该客户端订阅指定队列的消息。当服务端向该队列投递消息时,客户端通过通信通道接收消息,并进行相应处理。
二.服务端模块
2.1.数据管理模块
2.1.1.交换机数据管理模块
1.要管理的数据
该模块负责维护交换机的基本数据信息,每个交换机包含以下属性:
-
交换机名称:全局唯一标识。
-
交换机类型:决定消息的转发机制,主要包括以下三种常见类型:
-
直接交换 :若消息的
routing_key与队列绑定的binding_key完全一致,则消息被投递至该队列。 -
广播交换:消息将被转发至绑定在该交换机上的所有队列。
-
主题交换 :根据
routing_key与binding_key的模式匹配规则进行路由,匹配成功则消息投递至对应队列。
-
-
持久化标志:标识该交换机是否需要进行持久化存储。
-
其他信息......
2. 提供的管理操作
-
创建交换机:如果指定名称的交换机不存在,则创建新交换机;若已存在,则直接返回。
-
删除交换机:删除指定交换机。注意:每个交换机都会绑定一个或者多个队列,这就意味着会有一个或者多个的绑定信息,我们删除交换机的时候,我们也需要将它删除。、
-
获取指定名称的交换机:根据名称查询并返回对应交换机信息。
-
获取交换机数量:返回当前系统中存在的交换机总数。
2.1.2.队列数据管理模块
1.要管理的数据
- 队列的名称:唯一标识
- 持久化存储标志:决定了是否将队列信息持久化存储,也决定了队列里面的消息是否需要持久化
- 是否独占标志:独占就指的是,只有当前客户端自己才能订阅队列消息,其他客户端不能订阅
2.提供的管理操作
- 创建队列
- 删除队列
- 获取指定队列信息
- 获取队列数量
- 获取所有队列的名称:当系统重启后,需要重新加载数据,加载历史信息(消息以队列为单元存储在文件里面,加载消息需要知道队列名称,因为后面消息存储的时候,存储文件以队列名称进行的取名)
一个队列如果持久化标志为false,则意外着重启后,队列没了,也没有客户端能订阅队列的信息,因此这个队列的消息如果持久化存储,是没有意义的,因此应该队列的持久化标志是false,那么它的消息也不需要持久化。
2.1.3.绑定数据管理模块
它的核心任务就是描述一下将哪个队列与哪个交换机绑定到了一起
1.要管理的数据
- 交换机名称
- 队列名称
- Binding Key(绑定键): 这个键用于和消息携带的路由键(Routing Key) 进行匹配,以决定消息是否要投递到该队列。
2.提供的管理操作
- 添加绑定
- 解除绑定
- 获取交换机相关的所有绑定信息(删除交换机的时候,需要删除相应绑定信息;当消息发布到交换机,交换机得通过这些
- 获取队列的所有绑定信息(删除队列的时候,需要删除相应绑定信息;)
- 获取绑定信息的数量
2.1.4.消息数据管理模块
- 消息属性
每条消息包含以下属性:
-
ID:消息的唯一标识。
-
持久化标志:标识该消息是否需要进行持久化存储。
-
**routing_key:**用于决定消息应发布到哪个队列。消息发布到交换机后,根据队列绑定的 binding_key 进行路由。
-
消息主体:消息的实际内容。
-
存储偏移量:消息按队列为单位存储在文件中,该偏移量表示消息相对于文件起始位置的字节偏移。
-
消息长度:从偏移量开始读取的字节数,用于解决网络传输或文件读取中的"粘包"问题。
-
是否有效标志:标记该消息是否已被逻辑删除(例如,被消费确认后标记为无效)。
说明:删除消息时并不立即进行数据搬移,而是仅将标志置为无效。当文件中有效消息占比低于 50%,且消息总数超过 2000 条时,会触发垃圾回收机制,重新整理文件中的数据。系统重启时,也仅重新加载有效消息,相当于执行一次垃圾回收。
垃圾回收:将所有有效消息从文件里面读取到内存,然后清空并截断文件,最后将有效消息重新写入文件中,从而保证文件中仅包含有效数据。
- 消息管理信息
管理方式
以队列为单元进行管理,所有消息操作都基于队列进行。
管理数据
-
消息链表:保存当前待推送给消费者的所有消息。
-
待确认消息哈希表:记录已推送给客户端但尚未收到确认的消息,收到确认后再从该结构中移除并执行持久化删除。
-
持久化消息哈希表:用于维护已持久化消息的实际存储位置。由于垃圾回收会改变消息在文件中的位置,该结构需要在垃圾回收后更新对应消息的存储偏移量(仅更新已持久化的消息)。
-
持久化有效消息数量:当前队列中已持久化且未被删除的消息数量。
-
持久化总消息数量:队列中所有已持久化消息的数量,用于判断是否触发垃圾回收。
- 主要操作与方法
队列消息结构管理
-
初始化队列消息结构:在队列创建时调用,初始化相关数据结构。
-
移除队列消息结构:在队列删除时调用,清理该队列对应的所有消息数据。
消息生命周期操作
-
向队列新增消息:将新消息加入消息链表,并视情况持久化。
-
获取队首消息 :从待发送消息链 表中取出最早的一条消息,并将其移入待确认消息哈希表。
-
对消息进行确认 :从待确认消息 哈希表中移除指定消息,并删除其在持久化存储中的数据。
-
恢复队列历史消息:通常在系统重启时执行,从持久化文件中加载有效消息到内存结构中。
-
垃圾回收:由持久化子模块执行,当有效消息数量占比低于 50% 且消息总数超过 2000 时触发,重新整理文件存储。
-
删除队列相关消息文件:当队列被删除时,清除其对应的所有持久化消息文件。
2.2.虚拟机数据管理模块
该模块是对上述四个数据管理模块(交换机、队列、绑定、消息)的整合与管理。
在消息队列系统中,一个虚拟机通常作为一个逻辑单元,包含完整的交换机、队列、绑定及消息数据,该模块负责协调这些数据的一致性与整体生命周期。
需要管理的数据
- 交换机数据管理句柄
- 队列数据管理句柄
- 绑定信息管理句柄
- 消息数据管理句柄
需要提供的操作
- 声明/删除交换机:注意------在删除交换机的时候需要删除相关的绑定信息
- 声明/删除队列:注意------在删除队列时,需要删除相关的绑定信息以及消息数据
- 队列的绑定/解除绑定:注意------绑定的时候,必须确认交换机和队列是存在的
- 获取指定队列的消息
- 对指定队列的指定消息进行确认
- 获取交换机相关的所有绑定信息:一条消息要发布给指定交换机的时候,交换机获取所有绑定信息,来确定消息要发布给哪个队列。
2.3.路由匹配模块
这个模块决定了一条消息是否能发布到指定的队列
这里就涉及到两个东西:
Routing Key(路由键)
-
属于每条消息,由生产者发送消息时指定。
-
一条消息只有一个 Routing Key。
Binding Key(绑定键)
-
属于每个绑定,而每个绑定连接一个交换机和一个队列。
-
一个队列可以有多个绑定 ,因此可以有多个不同的 Binding Key。
-
一个绑定就是一条规则:"如果消息的 Routing Key 匹配这个 Binding Key,就把消息送到这个队列"。
它们是怎么进行工作的?
现在,我们通过一个消息从生产到消费的完整流程,来理解这些组件如何协同工作,并具体解释 Exchange 和 Queue 的"多对多"关系。
步骤 1:生产者发送
生产者应用创建一个消息,为其指定一个路由键(Routing Key) ,然后将其发送到 Broker 上一个已知的 Exchange。
步骤 2:交换机路由
Exchange 收到消息后,会提取消息中的 Routing Key 。然后,它查看所有绑定(Binding) 到自身的规则列表。
步骤 3:绑定匹配与投递
对于每一条绑定规则,Exchange 会根据自身的类型 ,将消息的 Routing Key与绑定的 Binding Key进行匹配。
- 如果匹配成功,Exchange 就会将消息的一个副本投递到该绑定规则所指向的队列。
- 如果匹配失败,则跳过该队列。
- Exchange 会遍历所有绑定到它的规则,可能将消息投递到零个、一个或多个队列。
步骤 4:队列存储与消费
- 消息被投递到队列后,便存储在队列中。消费者应用从自己订阅的队列(中获取消息进行处理。
那么它们怎么进行匹配呢?
交换机的类型决定了Binding Key和Routing Key的匹配算法。
1. 直连交换机(Direct)
- 匹配规则:精确、完全相等的字符串匹配。
- Binding Key:必须是一个明确的字符串,如 "email"、"order.paid"。
- Routing Key:必须与Binding Key完全一致,消息才会被路由。
示例:
- 队列Q1绑定了 Binding Key: "error"。
- 生产者发送消息A (Routing Key: "info") -> 不匹配,消息A不会进入Q1。
- 生产者发送消息B (Routing Key: "error") -> 精确匹配,消息B进入Q1。
用途:点对点精确路由,常用于任务分发(如将不同的任务类型路由到不同的处理队列)。
2. 主题交换机(Topic)
匹配规则:通配符模式匹配。这是最灵活、最常用的路由方式。
Binding Key:是一个用点号.分隔的单词组成的模式,支持两个通配符:
- * (星号):匹配恰好一个单词。
-
(井号):匹配零个或多个单词。
Routing Key:也是一个用点号.分隔的单词组成的字符串(不能包含通配符)。
示例:
- 队列Q1绑定了 Binding Key: "*.stock.usd" -> 关心所有以.stock.usd结尾,且中间有一个任意单词的消息。
- 队列Q2绑定了 Binding Key: "nyse.#" -> 关心所有以nyse.开头的消息。
- 生产者发送消息 (Routing Key: "nyse.stock.usd") -> 同时匹配Q1和Q2,消息会进入两个队列(广播)。
- 生产者发送消息 (Routing Key: "forex.eur.usd") -> 只匹配Q1,消息进入Q1。
- 生产者发送消息 (Routing Key: "nyse") -> 只匹配Q2(#可以匹配零个单词),消息进入Q2。
用途:基于多重标准(如来源、严重级别、业务类型)的灵活消息路由,如日志系统、事件通知系统。
3. 扇出交换机(Fanout)
- 匹配规则:忽略Routing Key和Binding Key。
- 行为:它会将收到的所有消息无条件地广播到所有与之绑定的队列。
- Binding Key:在创建绑定时通常设置为空字符串""(但设置什么值都无所谓,因为不会被使用)。
- 用途:纯粹的广播/发布-订阅场景。
设计路由匹配模块
在这个模块里,本质没有需要管理的数据,只有向外提供的路由匹配操作
- 判断Binding Key和Routing Key是否成功匹配的接口
- 判断Routing Key是否符合我们的约定(只能由数字,字母,_ 构成)
- 判断Binding Key是否符合我们的规定(只能由数字,字母,_,*,#构成;注意:* (星号):匹配恰好一个单词;# (井号):匹配零个或多个单词)
2.4.消费者管理模块
本模块用于管理消息队列中的消费者(客户端)。系统中存在两种客户端角色:
-
发布消息客户端:负责向队列发送消息。
-
订阅消息客户端:负责从队列接收消息,仅此类客户端被视为消费者。
消费者数据的核心作用是:当其所订阅的队列有新消息时,系统能够根据消费者信息(尤其是网络连接)将消息准确推送给对应的客户端。消费者在接收消息后,需向Broker确认消息处理状态。
- 消费者信息
每个消费者包含以下关键属性:
| 属性 | 说明 |
|---|---|
| 消费者标识(tag) | 消费者的唯一标识符 |
| 订阅队列名称 | 所订阅的队列,用于消息路由与确认;当当前队列有消息,就会将消息推送给客户端;客户端收到消息后,需要对指定队列的消息进行确认 |
| 自动确认标志 | 当消费者从队列中获取消息后,必须向Broker告知该消息的最终处理状态。 控制消息确认模式: • 自动确认 :一旦Broker将消息通过网络投递给消费者,便立即将该消息从队列中标记为删除或直接删除。 • 手动确认 :消费者在成功处理完消息后,必须显式向Broker发送一个确认信号。Broker在收到此确认后,才会将消息从队列中安全地移除。 |
| 消费处理回调函数指针 | 当队列有消息时,调用此函数向对应客户端推送消息(内部逻辑固定) |
- 管理设计思想
消费者管理以队列为单元进行组织:
-
每个消费者只订阅特定的队列。
-
消息确认也以队列为基础进行。
-
当某个队列有消息到达时,系统从该队列的消费者列表中选取一个消费者 进行推送(默认采用RR轮转策略)。
- 队列消费者管理结构
数据成员
- 消费者链表:保存当前队列的所有消费者信息。消息推送时,采用RR轮转方式依次选取下一个消费者,确保每条消息仅由一个消费者处理。
成员方法(管理操作)
-
初始化队列消费者结构
-
删除队列消费者结构
-
向指定队列添加消费者
-
通过RR轮转获取下一个消费者
-
删除指定队列中的消费者
-
获取指定队列的消费者总数
-
判断指定队列的消费者列表是否为空
5.关键操作说明
| 操作 | 功能 |
|---|---|
| 新增消费者 | 将消费者加入对应队列的链表 |
| RR轮转获取消费者 | 从链表按轮转策略选取下一个消费者 |
| 删除消费者 | 从队列链表中移除指定消费者 |
| 队列消费者总数 | 返回当前队列的消费者数量 |
| 队列是否为空 | 判断当前队列是否有活跃消费者 |
2.5.通信通道管理模块
通信通道,也叫信道
网络通信的时候,必然是通过网络通信连接来完成的。
为了能充分的利用资源,因此对通信连接有进行了进一步的细化,细化出了通信通道,对于用户来说,一个通信通道,就是进行网络通信的载体,而一个真正的通信连接,可以创建出多个通信通道
每一个信道之间,在用户的眼中是是相互独立的,但是在本质的底层中它们使用同一个通信连接进行网络通信
因此,因为信道是用户眼中的一个通信通道,所以所有的网络通信服务都是由通信通道通过的。
信道提供的服务操作
- 声明/删除交换机
- 声明/删除队列
- 绑定/解除绑定队列与交换机
- 订阅队列消息/取消订阅队列消息
- 发布消息/队列消息确认
信道需要管理的消息
- 信道ID
- 信道关联的虚拟机
- 信道关联的消费者句柄:当信道关闭的时候,所有关联的消费者订阅都需要取消,相当于删除所有的相关消费者
- 工作线程池句柄 :信道进行了消息发布到指定队列操作,从指定队列获取一个消费者,对这条消息进行消费,将这条消息推送一个客户端的操作交给线程池执行,这个过程必须使用新线程来完成; 并非每个信道都有一个线程池,而是整个服务器有一个线程池,大家所有的信道都是通过一个线程池进行异步操作而已
信道管理
- 创建一个信道
- 关闭一个信道
- 根据信道ID获取对应的信道句柄
2.6.连接管理模块
其实就是一个网络通信连接
在这里我们使用Muduo库来实现底层通信,Muduo库本身就有Connection连接的概念和对象类
但是我们的连接里面,还有一个上层通信通道的概念,这个概念是Muduo库里面是没有的
我们需要对Muduo库里面的Connection连接进行二次封装
管理数据
- Muduo库的通信连接
- 当前连接关联的信道关联句柄
管理的操作
- 创建信道
- 关闭信道
- 新增连接
- 关闭连接
- 获取指定连接信息
2.7.服务端BrokerServer模块
整合以上所有模块,并搭建网络通信服务器,实现与客户端网络通信,能够识别客户端请求,并提供客户端请求的处理服务。
管理信息:
- 虚拟机管理模块句柄
- 消费者管理模块句柄
- 连接管理模块句柄
- 工作线程池句柄
- muduo库通信所需元素...
这个模块其实功能整合模块,并不提供实质的功能性操作
这个模块最重要的是资源的整合,是一个资源的载体
- 一个服务器有一个工作线程池,其他所有的信道操作都是这同一个线程池
- 一个服务器有一个虚拟机,其他所有交换机,队列,绑定,消息的操作都是针对这个虚拟机进行的
- 一个服务器有一个消费者管理,
- 通信相关连接管理,协议处理模块句柄,也是一整个服务器有一套
三.客户端模块
3.1. 消费者管理模块
在订阅客户端中,当用户订阅某一队列的消息时,即创建一个对应的消费者。该模块负责维护消费者状态,并处理服务端推送过来的消息。
管理的信息
- 消费者标识
- 订阅的队列名称
- 自动确认标志
- 消息回调处理函数指针
当当前消费者订阅了某个队列的下线,这个队列有消息后就会将消息推送给客户端,这时候收到了消息则使用回调函数进行处理,处理完毕后根据自动确认标志决定是否进行消息确认
管理操作
- 新增消费者
- 删除消费者
- 查询当前消费者的信息
3.2. 通信通道管理模块
客户端中的通信通道与服务端的通道一一对应,负责在客户端与服务端之间传输数据。客户端通过通道向服务端发送请求,并接收服务端的响应或消息推送。
所有提供的操作与服务器雷同,因为客户端要给用户提供什么服务,服务器就要给客户端提供什么服务
管理信息
- 消费者管理句柄:每个信道都有自己相关的消费者
- 线程池句柄:对推送过来的消息进行回调处理,处理过程由工作线程来进行
- 信道关联的连接
信道提供的服务
- 声明/删除交换机
- 声明/删除队列
- 绑定/解除绑定队列与交换机
- 订阅队列消息/取消订阅队列消息
- 发布消息/队列消息确认
- 创建信道/关闭信道
信道的关联
- 信道的增删查
3.3. 连接管理模块
客户端通过一个网络连接与服务端通信,该连接可承载多个通道。该模块负责维护连接的建立、保持与关闭,并整合客户端所用资源,为用户提供透明的通信服务。
客户端连接的管理,本质上是对Muduo库里面的客户端TcpClient的二次封装
面对用户,不需要有客户端的概念,连接对于用户来说就是客户端,提供连接创建信道,提供信道完成自己所需服务
因此,当前客户端连接对用户来说就是一个资源的载体
管理操作:
- 连接服务器
- 创建信道
- 关闭信道
- 关闭连接
管理的资源
- 工作线程池
- 连接关联的信道关联句柄
3.4.异步工作池模块
其实这里面就2个东西
- Muduo库里面的客户端TcpClient模块需要一个EventLoopThread模块进行IO事件监控
- 收到推送消息后,需要对推送过来的消息进行处理,因此需要一个线程池来帮助我们完成消息的处理过程
将异步工作池模块独立出来的原因:
- 多个连接用一个EventLoopThread模块进行IO事件监控就够了
- 所有的推送消息处理只需要一个线程池就够了
- 并不需要每个连接都有一个EventLoop,也不需要每个信道的消息处理都有自己的线程池
四.模块关系图



接下来我们看另外一幅



还是很清晰明了的吧