- 路由匹配模块
-
- 一、模块概述
- 二、核心语法规范
-
- [1. 路由键 RoutingKey 合法规则](#1. 路由键 RoutingKey 合法规则)
- [2. 绑定键 BindingKey 合法规则](#2. 绑定键 BindingKey 合法规则)
- [3. Topic 通配符语义](#3. Topic 通配符语义)
- 三、完整源码实现
- 四、核心设计亮点
-
- [1. 前置合法性强校验](#1. 前置合法性强校验)
- [2. 约束通配符非法组合](#2. 约束通配符非法组合)
- [3. 一维滚动 DP 实现 Topic 匹配](#3. 一维滚动 DP 实现 Topic 匹配)
- [4. 多交换机类型统一封装](#4. 多交换机类型统一封装)
- 五、单元测试验证
- 消费者管理模块
-
- 一、模块概述
- 二、核心结构定义
-
- [1. Consumer 消费者实体](#1. Consumer 消费者实体)
- [2. QueueConsumer 队列级消费者管理](#2. QueueConsumer 队列级消费者管理)
- [3. ConsumerManager 全局消费者管理](#3. ConsumerManager 全局消费者管理)
- 三、核心功能与设计亮点
-
- [1. 三层架构,职责清晰](#1. 三层架构,职责清晰)
- [2. Round-Robin 轮询负载均衡](#2. Round-Robin 轮询负载均衡)
- [3. 高并发安全设计](#3. 高并发安全设计)
- [4. 完整生命周期管理](#4. 完整生命周期管理)
- [5. 完善的异常处理](#5. 完善的异常处理)
- 四、完整代码
- 五、单元测试验证
路由匹配模块
一、模块概述
路由模块是自研消息队列的核心基础模块,负责路由键合法性校验、绑定键合法性校验、交换机消息路由匹配 三大核心能力。
适配直连(DIRECT)、广播(FANOUT)、主题(TOPIC)三种交换机类型,严格遵循 RabbitMQ 路由语义规范,同时做绑定键通配符语法合法性强校验,杜绝非法通配符格式,保障路由匹配逻辑严谨、无消息错配、漏配。
模块核心能力:
- 路由键合法性校验:仅允许大小写字母、数字、下划线、小数点;
- 绑定键合法性校验:在路由键基础上新增
*#通配符,并做通配符语法规则校验; - 多类型交换机路由匹配:DIRECT 精确匹配、FANOUT 全匹配、TOPIC 通配符层级匹配;
- 采用一维滚动 DP 实现 Topic 匹配,空间复杂度更低、执行效率高,适配高并发消息投递场景。
二、核心语法规范
1. 路由键 RoutingKey 合法规则
仅允许字符:a-z、A-Z、0-9、.、_
不允许任何特殊符号、空格、通配符。
2. 绑定键 BindingKey 合法规则
合法字符:a-z、A-Z、0-9、.、_、*、#
额外语法约束:
*#必须独立作为一个层级,不能和普通字符混杂(如mu*sic非法);- 禁止通配符连续非法组合:
#后不能接#/*,*后不能接#; - 借助合法性校验前置拦截非法格式,路由匹配层无需做语法容错。
3. Topic 通配符语义
*:匹配任意一个独立层级;#:匹配零个或多个任意层级;- 层级以小数点
.分割,按层级逐一匹配。
三、完整源码实现
mq_route.hpp
cpp
#ifndef __M_ROUTE_H__
#define __M_ROUTE_H__
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_msg.pb.h"
#include <vector>
#include <string>
namespace Fy_mq{
class Route{
public:
static bool isLegalRoutingKey(const std::string &routing_key){
// 合法字符:a~z, A~Z, 0~9, ., _
for (auto &ch : routing_key) {
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
(ch == '_' || ch == '.')) {
continue;
}
return false;
}
return true;
}
static bool isLegalBindingKey(const std::string &binding_key){
// 合法字符:a~z, A~Z, 0~9, ., _, *, #
for (auto &ch : binding_key) {
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
(ch == '_' || ch == '.' || ch == '*' || ch == '#')) {
continue;
}
return false;
}
// 通配符必须独立成段,不能与普通字符混杂
std::vector<std::string> sub_words;
StrHelper::split(binding_key,".",sub_words);
for(auto &word : sub_words){
if(word.size() > 1 && (word.find("*") != std::string::npos || word.find("#") != std::string::npos))
return false;
}
// 禁止通配符连续非法搭配
int n = sub_words.size();
for(int i = 1; i < n; ++i){
if(sub_words[i] == "#" && (sub_words[i - 1] == "*" || sub_words[i - 1] == "#")){
return false;
}else if(sub_words[i] == "*" && sub_words[i - 1] == "#"){
return false;
}
}
return true;
}
static bool route(ExchangeType type,const std::string &routing_key,const std::string &binding_key){
if(type == ExchangeType::DIRECT){
return routing_key == binding_key;
}else if(type == ExchangeType::FANOUT){
return true;
}else{
std::vector<std::string> rkeys,bkeys;
int n_rkeys = StrHelper::split(routing_key,".",rkeys);
int n_bkeys = StrHelper::split(binding_key,".",bkeys);
std::vector<bool> dp(n_bkeys + 1,false);
dp[0] = true;
if(n_bkeys && bkeys[0] == "#") dp[1] = true;
for(int i = 0;i < n_rkeys; ++i){
if(i)dp[0] = false;
bool pre = dp[0],tmp;
for(int j = 0;j < n_bkeys; ++j){
tmp = dp[j + 1];
if(rkeys[i] == bkeys[j] || bkeys[j] == "*"){
dp[j + 1] = pre;
}else if(bkeys[j] == "#"){
// # 匹配0个或多个层级
dp[j + 1] = dp[j] | dp[j + 1];
}else{
dp[j + 1] = false;
}
pre = tmp;
}
}
return dp[n_bkeys];
}
}
};
}
#endif
四、核心设计亮点
1. 前置合法性强校验
将路由键、绑定键的字符合法性 + 通配符语法合法性前置校验,非法格式直接拦截,避免进入路由匹配层做无效计算,同时规范 MQ 使用语法。
2. 约束通配符非法组合
禁止 ##、#.*、*# 等不符合 MQ 规范的写法,保证 Topic 路由语义唯一、可预期。
3. 一维滚动 DP 实现 Topic 匹配
摒弃传统二维 DP 冗余写法,采用一维滚动数组优化:
- 空间复杂度: O ( n ) O(n) O(n),n 为绑定键层级数;
- 通过
pre临时保存上一状态,避免数组覆盖丢失中间状态; - 精准实现
*单层级匹配、#零/多层级匹配,完全对标 RabbitMQ 路由语义。
4. 多交换机类型统一封装
- DIRECT:字符串精确相等匹配;
- FANOUT:所有队列直接匹配成功;
- TOPIC:基于层级拆分 + DP 动态规划通配符匹配;
上层业务只需调用统一route接口,无需感知底层匹配差异。
五、单元测试验证
基于 GTest 编写全覆盖测试用例,覆盖三大场景:
- 路由键合法/非法字符校验;
- 绑定键字符合法、通配符语法非法校验;
- Topic 各类通配符组合、层级匹配边界用例。
cpp
#include "../mqserver/mq_route.hpp"
#include <gtest/gtest.h>
class RouteTest : public testing::Environment {
public:
virtual void SetUp() override {
}
virtual void TearDown() override {
}
};
TEST(route_test,legal_routing_key) {
std::string rkey1 = "news.music.cpp";
std::string rkey2 = "news..music.pop";
std::string rkey3 = "news.,music.pop";
std::string rkey4 = "news.music_123.cpp";
ASSERT_EQ(Fy_mq::Router::isLegalRoutingKey(rkey1), true);
ASSERT_EQ(Fy_mq::Router::isLegalRoutingKey(rkey2), true);
ASSERT_EQ(Fy_mq::Router::isLegalRoutingKey(rkey3), false);
ASSERT_EQ(Fy_mq::Router::isLegalRoutingKey(rkey4), true);
}
TEST(route_test,legal_binding_key) {
std::string bkey1 = "news.music.pop";
std::string bkey2 = "news.#.music.pop";
std::string bkey3 = "news.#.*.music.pop";//
std::string bkey4 = "news.*.#.music.pop";//
std::string bkey5 = "news.#.#.music.pop";//
std::string bkey6 = "news.*.*.music.pop";
std::string bkey7 = "news.,music_123.pop";//
ASSERT_EQ(Fy_mq::Router::isLegalBindingKey(bkey1), true);
ASSERT_EQ(Fy_mq::Router::isLegalBindingKey(bkey2), true);
ASSERT_EQ(Fy_mq::Router::isLegalBindingKey(bkey3), false);
ASSERT_EQ(Fy_mq::Router::isLegalBindingKey(bkey4), false);
ASSERT_EQ(Fy_mq::Router::isLegalBindingKey(bkey5), false);
ASSERT_EQ(Fy_mq::Router::isLegalBindingKey(bkey6), true);
ASSERT_EQ(Fy_mq::Router::isLegalBindingKey(bkey7), false);
}
TEST(route_test,route) {
std::vector<std::string> bkeys = {
"aaa",
"aaa.bbb",
"aaa.bbb",
"aaa.bbb",
"aaa.#.bbb",
"aaa.bbb.#",
"#.bbb.ccc",
"aaa.bbb.ccc",
"aaa.*",
"aaa.*.bbb",
"*.aaa.bbb",
"#",
"aaa.#",
"aaa.#",
"aaa.#.ccc",
"aaa.#.ccc",
"aaa.#.ccc",
"#.ccc",
"#.ccc",
"aaa.#.ccc.ccc",
"aaa.#.bbb.*.bbb"
};
std::vector<std::string> rkeys = {
"aaa",
"aaa.bbb",
"aaa.bbb.ccc",
"aaa.ccc",
"aaa.bbb.ccc",
"aaa.ccc.bbb",
"aaa.bbb.ccc.ddd",
"aaa.bbb.ccc",
"aaa.bbb",
"aaa.bbb.ccc",
"aaa.bbb",
"aaa.bbb.ccc",
"aaa.bbb",
"aaa.bbb.ccc",
"aaa.ccc",
"aaa.bbb.ccc",
"aaa.aaa.bbb.ccc",
"ccc",
"aaa.bbb.ccc",
"aaa.bbb.ccc.ccc.ccc",
"aaa.ddd.ccc.bbb.eee.bbb"
};
std::vector<bool> result = {
true,
true,
false,
false,
false,
false,
false,
true,
true,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true
};
for (int i = 0; i < bkeys.size(); i++) {
ASSERT_EQ(Fy_mq::Router::route(Fy_mq::ExchangeType::TOPIC, rkeys[i], bkeys[i]), result[i]);
}
}
int main(int argc,char *argv[]){
testing::InitGoogleTest(&argc,argv);
testing::AddGlobalTestEnvironment(new RouteTest);
return RUN_ALL_TESTS();
}
测试结果
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from route_test
[ RUN ] route_test.legal_routing_key
[ OK ] route_test.legal_routing_key (0 ms)
[ RUN ] route_test.legal_binding_key
[ OK ] route_test.legal_binding_key (0 ms)
[ RUN ] route_test.route
[ OK ] route_test.route (0 ms)
[----------] 3 tests from route_test (1 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (1 ms total)
[ PASSED ] 3 tests.
所有用例全部通过
消费者管理模块
一、模块概述
消费者管理模块 是自研消息队列服务端的核心组件,主要负责消费者生命周期管理、队列与消费者关联维护、多消费者负载均衡调度,为消息可靠、均匀、高效投递提供底层支撑。
模块采用三层架构设计:
- Consumer:消费者实体,封装标识、队列、应答模式、回调函数;
- QueueConsumer:队列级消费者管理器,实现单队列下多消费者的增删、轮询选择;
- ConsumerManager:全局消费者管理中心,统一管理所有队列的消费者集合。
模块特性:
- 线程安全,支持高并发创建/删除/调度;
- 采用 Round-Robin 轮询负载均衡;
- 最小化锁粒度,性能高效;
- 完整的错误处理与日志输出;
- 智能指针管理,无内存泄漏。
二、核心结构定义
1. Consumer 消费者实体
封装单个消费者的所有信息:
tag:消费者唯一标识qname:订阅队列名称auto_ack:是否自动确认消息callback:消息到达后的业务处理回调
cpp
using ConsumerCallback = std::function<void(const std::string &,const BasicProperties *bp,const std::string &)>;
struct Consumer {
using ptr = std::shared_ptr<Consumer>;
std::string tag;
std::string qname;
bool auto_ack;
ConsumerCallback callback;
Consumer(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb);
~Consumer();
};
2. QueueConsumer 队列级消费者管理
以队列为维度,管理该队列下所有消费者:
- 创建/删除消费者
- RR 轮询选择消费者
- 判断队列是否存在可用消费者
- 线程安全访问
cpp
class QueueConsumer {
public:
using ptr = std::shared_ptr<QueueConsumer>;
QueueConsumer(const std::string &qname);
Consumer::ptr create(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb);
void remove(const std::string &ctag);
Consumer::ptr choose(); // 轮询选择
bool empty();
bool exists(const std::string &ctag);
void clear();
};
3. ConsumerManager 全局消费者管理
对外提供统一接口,管理所有队列的消费者:
- 初始化/销毁队列消费者管理器
- 创建/删除消费者
- 选择消费者、判空、查询等
cpp
class ConsumerManager {
public:
using ptr = std::shared_ptr<ConsumerManager>;
void initQueueConsumer(const std::string &qname);
void destroyQueueConsumer(const std::string &qname);
Consumer::ptr create(const std::string &ctag, const std::string &qname, bool ack_flag, const ConsumerCallback&cb);
void remove(const std::string &ctag, const std::string &qname);
Consumer::ptr choose(const std::string &qname);
bool empty(const std::string &qname);
bool exists(const std::string &ctag, const std::string &qname);
void clear();
};
三、核心功能与设计亮点
1. 三层架构,职责清晰
- Consumer:数据载体
- QueueConsumer:队列内调度与管理
- ConsumerManager:全局统一入口
结构解耦、易于维护、易于扩展。
2. Round-Robin 轮询负载均衡
同一队列多个消费者时,消息均匀轮转投递,避免消费者负载不均。
cpp
int idx = _rr_seq % _consumers.size();
++_rr_seq;
return _consumers[idx];
3. 高并发安全设计
- 所有共享变量访问加锁
- 最小化锁粒度:只在查找队列时加锁,拿到指针后立即释放
- 队列内部操作使用独立互斥锁,互不阻塞
4. 完整生命周期管理
- 创建消费者(确保 tag 唯一)
- 删除消费者
- 清空队列消费者
- 销毁队列消费者管理器
- 智能指针自动释放资源
5. 完善的异常处理
- 队列不存在 → 打印日志并返回空
- 重复创建消费者 → 返回空
- 空队列选择消费者 → 返回空
系统稳定、不崩溃、可预期。
四、完整代码
cpp
#ifndef __M_CONSUMER_H__
#define __M_CONSUMER_H__
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_msg.pb.h"
#include <iostream>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <vector>
#include <functional>
namespace Fy_mq{
using ConsumerCallback = std::function<void(const std::string &,const BasicProperties *bp,const std::string &)>;
struct Consumer{
using ptr = std::shared_ptr<Consumer>;
std::string tag;
std::string qname;
bool auto_ack;
ConsumerCallback callback;
Consumer(){
DLOG("new Consumer: %p", this);
}
Consumer(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb):
tag(ctag), qname(queue_name), auto_ack(ack_flag), callback(std::move(cb)) {
DLOG("new Consumer: %p", this);
}
~Consumer() {
DLOG("del Consumer: %p", this);
}
};
class QueueConsumer{
public:
using ptr = std::shared_ptr<QueueConsumer>;
QueueConsumer(const std::string &qname):_qname(qname),_rr_seq(0){}
Consumer::ptr create(const std::string &ctag,const std::string &queue_name,bool ack_flag,const ConsumerCallback &cb){
std::unique_lock<std::mutex> lock(_mutex);
for(auto &consumer : _consumers){
if(consumer->tag == ctag){
return Consumer::ptr();
}
}
auto consumer = std::make_shared<Consumer>(ctag,queue_name,ack_flag,cb);
_consumers.push_back(consumer);
return consumer;
}
void remove(const std::string &ctag){
std::unique_lock<std::mutex> lock(_mutex);
for(auto it = _consumers.begin(); it != _consumers.end(); ++it){
if((*it)->tag == ctag){
_consumers.erase(it);
break;
}
}
}
Consumer::ptr choose(){
std::unique_lock<std::mutex> lock(_mutex);
if(_consumers.empty()){
return Consumer::ptr();
}
int idx = _rr_seq % _consumers.size();
++_rr_seq;
return _consumers[idx];
}
bool empty(){
std::unique_lock<std::mutex> lock(_mutex);
return _consumers.empty();
}
bool exists(const std::string &ctag){
std::unique_lock<std::mutex> lock(_mutex);
for(auto &consumer : _consumers){
if(consumer->tag == ctag) return true;
}
return false;
}
void clear(){
std::unique_lock<std::mutex> lock(_mutex);
_consumers.clear();
_rr_seq = 0;
}
private:
std::string _qname;
std::mutex _mutex;
uint64_t _rr_seq;
std::vector<Consumer::ptr> _consumers;
};
class ConsumerManager {
public:
using ptr = std::shared_ptr<ConsumerManager>;
ConsumerManager(){}
void initQueueConsumer(const std::string &qname){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _qconsumers.find(qname);
if(it != _qconsumers.end()) return;
auto qconsumer = std::make_shared<QueueConsumer>(qname);
_qconsumers.insert(std::make_pair(qname,qconsumer));
}
void destroyQueueConsumer(const std::string &qname){
std::unique_lock<std::mutex> lock(_mutex);
_qconsumers.erase(qname);
}
Consumer::ptr create(const std::string &ctag,const std::string &qname,bool ack_flag,const ConsumerCallback&cb){
QueueConsumer::ptr qcp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _qconsumers.find(qname);
if(it == _qconsumers.end()){
DLOG("新增消费者失败,未找到队列消费者句柄%s",qname.c_str());
return Consumer::ptr();
}
qcp = it->second;
}
return qcp->create(ctag,qname,ack_flag,cb);
}
void remove(const std::string &ctag,const std::string &qname){
QueueConsumer::ptr qcp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _qconsumers.find(qname);
if(it == _qconsumers.end()){
DLOG("删除消费者失败,未找到队列消费者句柄%s",qname.c_str());
return;
}
qcp = it->second;
}
qcp->remove(ctag);
}
Consumer::ptr choose(const std::string &qname){
QueueConsumer::ptr qcp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _qconsumers.find(qname);
if(it == _qconsumers.end()){
DLOG("未找到队列消费者句柄%s",qname.c_str());
return Consumer::ptr();
}
qcp = it->second;
}
return qcp->choose();
}
bool empty(const std::string &qname){
QueueConsumer::ptr qcp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _qconsumers.find(qname);
if(it == _qconsumers.end()){
DLOG("未找到队列消费者句柄%s",qname.c_str());
return false;
}
qcp = it->second;
}
return qcp->empty();
}
bool exists(const std::string &ctag,const std::string &qname){
QueueConsumer::ptr qcp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _qconsumers.find(qname);
if(it == _qconsumers.end()){
DLOG("未找到队列消费者句柄%s",qname.c_str());
return false;
}
qcp = it->second;
}
return qcp->exists(ctag);
}
void clear(){
std::unique_lock<std::mutex> lock(_mutex);
_qconsumers.clear();
}
private:
std::mutex _mutex;
std::unordered_map<std::string, QueueConsumer::ptr> _qconsumers;
};
}
#endif
五、单元测试验证
测试内容
- 消费者创建与存在性判断
- 消费者删除
- 轮询调度选择
cpp
#include "../mqserver/mq_consumer.hpp"
#include <gtest/gtest.h>
Fy_mq::ConsumerManager::ptr cmp;
class ConsumerTest : public testing::Environment {
public:
virtual void SetUp() override {
cmp = std::make_shared<Fy_mq::ConsumerManager>();
cmp->initQueueConsumer("queue1");
}
virtual void TearDown() override {
cmp->clear();
}
};
void cb(const std::string &tag,const Fy_mq::BasicProperties *bp,const std::string &body){
std::cout<< tag <<" 消费了消息: " << body << std::endl;
}
TEST(consumer_test,insert_test){
cmp->create("consumer1","queue1",false,cb);
cmp->create("consumer2","queue1",false,cb);
cmp->create("consumer3","queue1",false,cb);
ASSERT_EQ(cmp->exists("consumer1", "queue1"), true);
ASSERT_EQ(cmp->exists("consumer2", "queue1"), true);
ASSERT_EQ(cmp->exists("consumer3", "queue1"), true);
}
TEST(consumer_test,remove_test){
cmp->remove("consumer1", "queue1");
ASSERT_EQ(cmp->exists("consumer1", "queue1"), false);
ASSERT_EQ(cmp->exists("consumer2", "queue1"), true);
ASSERT_EQ(cmp->exists("consumer3", "queue1"), true);
}
TEST(consumer_test,choose_test){
Fy_mq::Consumer::ptr cp = cmp->choose("queue1");
ASSERT_NE(cp.get(),nullptr);
ASSERT_EQ(cp->tag,"consumer2");
cp = cmp->choose("queue1");
ASSERT_NE(cp.get(),nullptr);
ASSERT_EQ(cp->tag,"consumer3");
cp = cmp->choose("queue1");
ASSERT_NE(cp.get(),nullptr);
ASSERT_EQ(cp->tag,"consumer2");
}
int main(int argc,char *argv[]){
testing::InitGoogleTest(&argc,argv);
testing::AddGlobalTestEnvironment(new ConsumerTest);
return RUN_ALL_TESTS();
}
测试结果
[ RUN ] consumer_test.insert_test
[ RUN ] consumer_test.remove_test
[ RUN ] consumer_test.choose_test
[ PASSED ] 3 tests.