【RabbitMQ 项目】服务端:路由交换模块

文章目录

一.概念辨析

  1. 这个模块是干啥的?
    用户给服务器发送一条消息,消息中只指定了交换机,没有指定具体要发布到哪些队列。这个时候服务器模块就需要使用我们的路由交换模块,选择与之匹配的队列了
  2. 怎么匹配?
    一个交换机和一个绑定的队列是否匹配,取决于两个要素:
    具体如下:
  • DIRECT------直接交换:routinng_key 和 binding_key 相等则匹配成功
  • FANOUT------广播交换:不用做任何比较,直接匹配成功(注意前提是这个队列和交换机有绑定关系)
  • TOPIC------主题交换:只有 routing_key 和 binding_key"匹配",交换机和队列才匹配成功
  1. routing_key 和 binding_key 构成
    routing_key:由若干个单词构成,单词之间用"."分开,单词由字母,数字和下划线构成
    binding_key:在 routing_key 的基础上多了两个通配符"*"和"#",可以匹配一个单词,#可以匹配 0 个或多个单词
    规定
    和#只能单独存在,不能和其它字符一起组成一个单词,并且#附近不能再有通配符了,因为#已经可以匹配任意多个单词了,再加通配符是没有意义的

二.编写思路

本模块是一个纯算法模块,不管理数据,指向外提供一些静态方法,供服务器模块使用。
方法:

  1. 判断 binding_key 是否合法:当要新建一个 binding 时,服务器模块会先检查用户给的 binding_key 是否合法
    先遍历判断是否有不合法的字符,然后检查每个单词合法性,即不能通配符和普通字符混搭,最后检查#附近是否有通配符
  2. 判断 routing_key 是否合法:服务器模块检查用户发来的消息中的 routing_key 是否合法,如果这都不合法,那根本无法路由交换,选择与之匹配的队列了
    只需遍历判断是否有不合法字符即可
  3. 判断 routing_key 是否能和 binding_key 匹配
  • 直接交换:判断 routing_key 和 binding_key 是否相等
  • 广播交换:直接返回 true
  • 主题交换:两个数组的动态规划问题
    首先把 binding_key,routing_key 分割成一个个单词构成的单词的数组
    建一个 dp 表,dp[i][j]的含义是 binding 单词表的[0,i]部分,和 routing 单词表的[0, j]部分是否匹配
    dp[i][j]怎么填?考虑两个单词表的第 i 个单词和第 j 个单词
    分类讨论:
  1. 如果 binding 表和 routing 表最后一个单词相同,或者 binding 表最后是"*",那么最后一个单词就匹配上了,整体能否匹配取决于他们前面部分能否匹配,取决于 dp[i-1][j-1]的状态

    i, j\]这个格子的左上方如果为真,它就为真

    1. #与和 routing 表最后一个单词匹配,并且#消去,整体能否匹配取决于 dp[i-1][j-1]
    2. #和 routing 表最后一个单词匹配,但是#不消去,继续向前匹配,整体能否匹配取决于 dp[i][j-1]
    3. #不和最后一个单词匹配,但是#消去,相当于#匹配了 0 个单词,整体能否匹配去取决于 dp[i-1][j]
      所以[i,j]这个格子左上方,左方,上方,任何一个为真即为真

三.代码实践

Route.hpp:

cpp 复制代码
#pragma once
#include "../common/Util.hpp"
#include "../common/Type.hpp"
namespace ns_route
{
    class Router
    {
    public:
        static bool isLegalRoutingKey(const std::string &routingKey)
        {
            // 只能有字母,数字和下划线
            for (auto ch : routingKey)
            {
                if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' ||
                    (ch >= '0' && ch <= '9') || ch == '.')
                {
                    continue;
                }
                else
                {
                    return false;
                }
            }
            return true;
        }

        static bool isLegalBindingKey(const std::string &bindingKey)
        {
            // 先判断是否有非法字符
            for (auto ch : bindingKey)
            {
                if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || (ch >= '0' && ch <= '9') ||
                    ch == '*' || ch == '#' || ch == '.')
                {
                    continue;
                }
                else
                {
                    return false;
                }
            }

            // 检查每个单词是否合法:通配符只能单独存在
            std::vector<std::string> words;
            int wordNum = ns_util::StringUtil::split(bindingKey, ".", &words, ns_util::SepType::SPLITEASCHAR);
            for (const auto &word : words)
            {
                if (word.size() > 1 &&
                    (word.find('*', 0) != std::string::npos || word.find('#', 0) != std::string::npos))
                {
                    //LOG(INFO) << "单词不合法, bindingKey:" << bindingKey << ", word: " << word << endl;
                    return false;
                }
            }

            // 检查'#'附近是否有通配符
            for (int i = 1; i < wordNum; i++)
            {
                if (words[i] == "#")
                {
                    if (words[i - 1] == "*" || words[i - 1] == "#")
                    {
                        //LOG(INFO) << "#附近有通配符, bindingKey: " << bindingKey << endl;
                        return false;
                    }
                }
            }

            return true;
        }

        static bool isMatched(const std::string &routingKey, const std::string &bindingKey, ns_data::ExchangeType type)
        {
            if (type == ns_data::ExchangeType::DIRECT)
            {
                return routingKey == bindingKey;
            }
            else if (type == ns_data::ExchangeType::FANOUT)
            {
                return true;
            }

            //主题交换
            std::vector<std::string> routingWords;
            std::vector<std::string> bindingWords;
            int m = ns_util::StringUtil::split(bindingKey, ".", &bindingWords, ns_util::SepType::SPLITEASCHAR);
            int n = ns_util::StringUtil::split(routingKey, ".", &routingWords, ns_util::SepType::SPLITEASCHAR);

            // dp[i]对[j]表示对于bindingKey和routingKey分割出来的单词表,前者[0,i]和后者[0,j]是否匹配
            std::vector<std::vector<bool>> dp(m + 1, std::vector<bool>(n + 1, false));
            dp[0][0] = true; // 当两者都为空时匹配成功

            // 如果bindingWords的第一个单词是"#",要特殊处理
            if (bindingWords[0] == "#")
            {
                dp[1][0] = true;
            }
            // #后面不可能继续跟#,无需往后判断了

            for (int i = 1; i <= m; i++)
            {
                for (int j = 1; j <= n; j++)
                {
                    int x = i - 1;
                    int y = j - 1;
                    if (bindingWords[x] == "#")
                    {
                        // 考虑二者的最后一个单词:
                        // 1.#和routingWords[y]匹配,并且#消去了
                        // 2.#和routingWords[y]匹配,但#留下继续向前匹配
                        // 3.#不和routingWords[y]匹配,但#消去了
                        dp[i][j] = dp[i - 1][j - 1] || dp[i][j - 1] || dp[i - 1][j];
                    }
                    else // 普通单词或者"*"
                    {
                        if (bindingWords[x] == "*" || bindingWords[x] == routingWords[y])
                        {
                            dp[i][j] = dp[i - 1][j - 1];
                        }
                    }
                }
            } // end of for(int i)
            return dp[m][n];
        }
    };
}
相关推荐
回家路上绕了弯4 小时前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
用户8307196840826 小时前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840822 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者3 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者5 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧6 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖6 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农6 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者6 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀6 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式