交换机路由管理模块

1. 交换路由管理

客户端将消息发布到指定的交换机,交换机这时候要考虑这条数据该放入到哪些与自己绑定的队列中,而这个考量是通过交换机类型以及匹配规则来决定的:

  1. 广播交换:直接将消息交给所有绑定的队列,无需匹配
  2. 直接交换:队列绑定信息中的 binding_key 与消息中的 routing_key 一致则匹配成功,否则失败。
  3. 主题交换:只有匹配队列主题的消息才会被放入队列中

其中广播交换和直接交换,都非常简单,唯一较为难以理解的是主题交换。

在这里我们需要先对 binding_key 和 routing_key 作以了解:

binding_key

是由数字字母下划线构成的, 并且使用 . 分成若干部分,并支持 * 和 # 通配符。

例如:news.music.#,这用于表示交换机绑定的当前队列是一个用于发布音乐新闻的队列。

  • 支持 * 和 # 两种通配符, 但是 * # 只能作为 . 切分出来的独立部分, 不能和其他数字字母混用,
    • 比如 a.*.b 是合法的, a.*a.b 是不合法的
    • * 可以匹配任意一个单词(注意是单词不是字母)
    • 可以匹配零个或者多个任意单词(注意是单词不是字母)

  • 注意事项: a.#.b
    • 一个单词中不能既出现 * 又出现 #, 也就是,一个单词中只能有一个通配符,且必须独立存在
    • #通配符两边不能出现其他通配符,因为 # 可以匹配任意多个任意单词,因此连续出现是没有意义的。

routing_key

是由数据、字母和下划线构成, 并且可以使用 . 划分成若干部分。

例如:news.music.pop,这用于表示当前发布的消息是一个流行音乐的新闻.

比如,在进行队列绑定时,某队列的 binding_key 约定为:news.music.#表示这个队列用于发布音乐新闻。而这时候客户端发布了一条消息,其中 routing_key 为: news.music.pop 则可以匹配成功, 而如果发布消息的 routing_key 为:news.sport.football,这时候就会匹配失败。

cpp 复制代码
#ifndef __M_ROUTE_H__
#define __M_ROUTE_H__
#include <iostream>
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"

namespace rabbitmq
{
    class Router
    {
    public:
    static bool isLegalRoutingKey(const std::string &routing_key) 
    {
        //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) 
    {
        //1. 判断是否包含有非法字符, 合法字符: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;
            }
        //2. *和#必须独立存在:  news.music#.*.#
        std::vector<std::string> sub_words;
        StringHelper::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;
                }
            }
        //3. *和#不能连续出现
        for(int i = 1; i < sub_words.size(); i++)
            {
                if (sub_words[i] == "#" && sub_words[i - 1] == "*") 
                {
                    return false;
                }
                if (sub_words[i] == "#" && sub_words[i - 1] == "#") 
                {
                    return false;
                }
                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;
        }
        //主题交换:要进行模式匹配    news.#   &   news.music.pop
        //1. 将binding_key与routing_key进行字符串分割,得到各个的单词数组
        std::vector<std::string> bkeys, rkeys;
        int n_bkey = StringHelper::split(binding_key, ".", bkeys);
        int n_rkey = StringHelper::split(routing_key, ".", rkeys);
        //2. 定义标记数组,并初始化[0][0]位置为true,其他位置为false
            std::vector<std::vector<bool>> dp(n_bkey + 1, std::vector<bool>(n_rkey + 1, false));
            dp[0][0] = true;
            //3. 如果binding_key以#起始,则将#对应行的第0列置为1.
            for(int i = 1; i <= bkeys.size(); i++) 
            {
                if (bkeys[i - 1] == "#") 
                {
                    dp[i][0] = true;
                    continue;
                }
                break;
            }
            //4. 使用routing_key中的每个单词与binding_key中的每个单词进行匹配并标记数组
            for (int i = 1; i <= n_bkey; i++) 
            {
                for (int j = 1; j <= n_rkey; j++) 
                {
                    //如果当前bkey是个*,或者两个单词相同,表示单词匹配成功,则从左上方继承结果
                    if (bkeys[i - 1] == rkeys[j - 1] || bkeys[i - 1] == "*") 
                    {
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                    else if (bkeys[i - 1] == "#") 
                    {
                        //如果当前bkey是个#,则需要从左上,左边,上边继承结果
                        dp[i][j] = dp[i - 1][j - 1] || dp[i][j - 1] || dp[i - 1][j];
                    }
                }
            }
            return dp[n_bkey][n_rkey];
        }
    };
}

#endif

2. 测试

binding_key和routing_key的合法测试以及二者的匹配测试:

cpp 复制代码
#include "../mqserver/route.hpp"
#include <gtest/gtest.h>

class QueueTest : public testing::Environment 
{
public:
    virtual void SetUp() override {}
    virtual void TearDown() override 
    {
        //bmp->clear();
    }
};

TEST(route_test, legal_routing_key) 
{
    std::string rkey1 = "news.music.pop";
    std::string rkey2 = "news..music.pop";
    std::string rkey3 = "news.,music.pop";
    std::string rkey4 = "news.music_123.pop";
    ASSERT_EQ(rabbitmq::Router::isLegalRoutingKey(rkey1), true);
    ASSERT_EQ(rabbitmq::Router::isLegalRoutingKey(rkey2), true);
    ASSERT_EQ(rabbitmq::Router::isLegalRoutingKey(rkey3), false);
    ASSERT_EQ(rabbitmq::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(rabbitmq::Router::isLegalBindingKey(bkey1), true);
    ASSERT_EQ(rabbitmq::Router::isLegalBindingKey(bkey2), true);
    ASSERT_EQ(rabbitmq::Router::isLegalBindingKey(bkey3), false);
    ASSERT_EQ(rabbitmq::Router::isLegalBindingKey(bkey4), false);
    ASSERT_EQ(rabbitmq::Router::isLegalBindingKey(bkey5), false);
    ASSERT_EQ(rabbitmq::Router::isLegalBindingKey(bkey6), true);
    ASSERT_EQ(rabbitmq::Router::isLegalBindingKey(bkey7), false);
}

TEST(route_test, route) 
{
// aaa                  aaa                     true
// aaa.bbb              aaa.bbb                 true
// aaa.bbb              aaa.bbb.ccc             false
// aaa.bbb              aaa.ccc                 false
// aaa.#.bbb            aaa.bbb.ccc             false
// aaa.bbb.#            aaa.ccc.bbb             false
// #.bbb.ccc            aaa.bbb.ccc.ddd         false
// aaa.bbb.ccc          aaa.bbb.ccc             true
// aaa.*                aaa.bbb                 true
// aaa.*.bbb            aaa.bbb.ccc             false
// *.aaa.bbb            aaa.bbb                 false
// #                    aaa.bbb.ccc             true
// aaa.#                aaa.bbb                 true
// aaa.#                aaa.bbb.ccc             true
// aaa.#.ccc            aaa.ccc                 true
// aaa.#.ccc            aaa.bbb.ccc             true
// aaa.#.ccc            aaa.aaa.bbb.ccc         true
// #.ccc                ccc                     true
// #.ccc                aaa.bbb.ccc             true
// aaa.#.ccc.ccc        aaa.bbb.ccc.ccc.ccc     true
// aaa.#.bbb.*.bbb      aaa.ddd.ccc.bbb.eee.bbb true
    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(rabbitmq::Router::route(rabbitmq::ExchangeType::TOPIC, rkeys[i], bkeys[i]), result[i]);
    }
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new QueueTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_routetest.cpp ../mqcommon/msg.pb.cc -o mq_routetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_routetest
[==========] 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 (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.
相关推荐
三点水-here1 小时前
03 - KV Cache与批处理:大模型推理的内存管理核心技术
服务器·人工智能·ai编程
yttandb2 小时前
linux的基础命令
linux·运维·服务器
未来之窗软件服务2 小时前
服务器运维(三十五)数字证书TLS 版本设备对照表—东方仙盟
运维·服务器·服务器运维·仙盟创梦ide·东方仙盟
lqj_本人2 小时前
Flutter三方库适配OpenHarmony【apple_product_name】设备型号标识符转换原理
运维·服务器·flutter
以太浮标2 小时前
华为eNSP综合实验之- 通过SSH远程登陆设备
服务器·网络·ssh
未来之窗软件服务2 小时前
服务器运维(三十七)日志分析redis日志工具—东方仙盟
运维·服务器·服务器运维·仙盟创梦ide·东方仙盟
ruxshui3 小时前
# Linux diff命令使用
linux·运维·服务器
枷锁—sha3 小时前
【SRC】前后端分离与API接口渗透
服务器·网络·安全·网络安全·系统安全
柏木乃一3 小时前
Linux进程信号(1):信号概述,信号产生part 1
linux·运维·服务器·c++·信号·signal