仿RabbitMQ实现消息队列-服务端核心模块实现(1)

项目文件结构

bash 复制代码
bitmq/ 
|-- mqdemo 
|-- mqclient 
|-- mqcommon 
|-- mqserver 
|-- mqtest 
|-- mqthird 
  • mqdemo:编写一些功能用例时所在的目录
  • mqcommon: 公共模块代码(线程池,数据库访问,文件访问,日志打印,pb相关,以及其他的一些琐碎功能模块代码)
  • mqclient: 客户端模块代码
  • mqserver: 服务器模块代码
  • mqtest: 单元测试
  • mqthird: 用到的第三方库存放目录

日志打印工具实现

cpp 复制代码
#include <iostream>
#include <ctime>
#include <cstdio>

// 日志级别
#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL

// 颜色宏定义
#define COLOR_NONE  "\033[0m"
#define COLOR_DBG   "\033[33m"  // 黄色
#define COLOR_INF   "\033[32m"  // 绿色
#define COLOR_ERR   "\033[31m"  // 红色

#define LOG(lev_color, lev_str, level, format,...) \
do{ \
    if(level >= DEFAULT_LEVEL){ \
        time_t t = time(nullptr); \
        struct tm *ptm = localtime(&t); \
        char time_str[32]; \
        strftime(time_str,31,"%H:%M:%S",ptm); \
        printf("[%s%s%s][%s][%s:%d]\t" format "\n",\
        lev_color, lev_str, COLOR_NONE ,time_str, __FILE__, __LINE__, ##__VA_ARGS__); \
    } \
}while(0)

#define DLOG(format,...) LOG(COLOR_DBG, "DBG", DBG_LEVEL, format,##__VA_ARGS__)
#define ILOG(format,...) LOG(COLOR_INF, "INF", INF_LEVEL, format,##__VA_ARGS__)
#define ELOG(format,...) LOG(COLOR_ERR, "ERR", ERR_LEVEL, format,##__VA_ARGS__)

int main(){
    DLOG("调试日志 hello");
    ILOG("普通日志 num = %d", 666);
    ELOG("错误日志 出错啦");
    return 0;
}

运行结果:

很好,那么我们就可以封装成头文件形式:

cpp 复制代码
#ifndef __M_LOG_H__
#define __M_LOG_H__
#include <iostream>
#include <ctime>
#include <cstdio>

// 日志级别
#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL

// 颜色宏定义
#define COLOR_NONE  "\033[0m"
#define COLOR_DBG   "\033[33m"  // 黄色
#define COLOR_INF   "\033[32m"  // 绿色
#define COLOR_ERR   "\033[31m"  // 红色

#define LOG(lev_color, lev_str, level, format,...) \
do{ \
    if(level >= DEFAULT_LEVEL){ \
        time_t t = time(nullptr); \
        struct tm *ptm = localtime(&t); \
        char time_str[32]; \
        strftime(time_str,31,"%H:%M:%S",ptm); \
        printf("[%s%s%s][%s][%s:%d]\t" format "\n",\
        lev_color, lev_str, COLOR_NONE ,time_str, __FILE__, __LINE__, ##__VA_ARGS__); \
    } \
}while(0)

#define DLOG(format,...) LOG(COLOR_DBG, "DBG", DBG_LEVEL, format,##__VA_ARGS__)
#define ILOG(format,...) LOG(COLOR_INF, "INF", INF_LEVEL, format,##__VA_ARGS__)
#define ELOG(format,...) LOG(COLOR_ERR, "ERR", ERR_LEVEL, format,##__VA_ARGS__)
#endif

工具类实现

sqlite 基础操作类

根据之前的sqlite3基础使用里封装的代码,稍加改造即可:

cpp 复制代码
class SqliteHelper{
public:
    typedef int(*SqliteCallback)(void*,int,char**,char**);

    SqliteHelper(const std::string &dbfile) :_dbfile(dbfile),_handler(nullptr){}
    
    bool open(int safe_level = SQLITE_OPEN_FULLMUTEX){
        int ret = sqlite3_open_v2(_dbfile.c_str(),&_handler,SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_level,nullptr);
        if(ret != SQLITE_OK){
            ELOG("创建/打开sqlite数据库失败:%s",sqlite3_errmsg(_handler));
            return false;
        }
        return true;
    }

    bool exec(const std::string &sql,SqliteCallback cb,void*arg){
        int ret = sqlite3_exec(_handler,sql.c_str(),cb,arg,nullptr);
        if(ret != SQLITE_OK){
            ELOG("%s 执行语句失败: %s",sql.c_str(),sqlite3_errmsg(_handler));
            return false;
        }
        return true;
    }

    bool close(){
        if(_handler) {
            sqlite3_close_v2(_handler);
            return true;
        }
        return false;
    }
private:
    std::string _dbfile;
    sqlite3 *_handler;
};

字符串操作类

实际上我们只需要实现一个字符串分割的功能,根据传入的字符串和分割字符串,将字符串分割成子字符串然后放进vector中:

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>

class StrHelper{
public:
    static size_t split(const std::string &str, const std::string &sep, std::vector<std::string> &result){
        size_t pos,idx = 0,len = str.size(),seplen = sep.size();
        while(idx < len){
            pos = str.find(sep,idx);
            if(pos == std::string::npos){
                result.emplace_back(str.substr(idx));
                break;
            }else if(pos == idx){
                idx += seplen;
                continue;
            }else{
                result.emplace_back(str.substr(idx,pos-idx));
                idx = pos + seplen;
            }
        }
        return result.size();
    }
};

int main(){
    std::string str = "...news...music.#.pop...";
    std::vector<std::string> arry;
    int n = StrHelper::split(str,".",arry);
    for(auto &s:arry){
        std::cout<<s<<std::endl;
    }
    return 0;
}

运行结果:

bash 复制代码
news
music
#
pop

UUID 生成器类

UUID(Universally Unique Identifier),即通用唯一识别码 ,由 32 位 16 进制数字组成。

标准格式以连字符分为五段,采用 8-4-4-4-12 的结构,示例:
550e8400-e29b-41d4-a716-446655440000

本项目 UUID 生成规则:

  • 采用 16 字节数组 生成 32 位 16 进制字符串
  • 8 字节:生成随机数字
  • 8 字节:使用有序序号
  • 组合后确保全局唯一,同时可通过序号区分数据顺序

具体实现:

cpp 复制代码
#include <iostream>
#include <random>
#include <sstream>
#include <iomanip>
#include <atomic>

static std::string uuid() {
    static std::mt19937_64 generator(std::random_device{}()); // random_device机器随机数,效率低,当作种子,生成伪随机数
    std::uniform_int_distribution<int> dist(0, 255);

    std::stringstream ss;
    ss << std::hex << std::setfill('0');

    for (int i = 0; i < 8; ++i) {
        ss << std::setw(2) << dist(generator);
        if (i == 3 || i == 5 || i == 7) {
            ss << '-';
        }
    }

    static std::atomic<size_t> seq(1);
    size_t num = seq.fetch_add(1);

    for (int i = 7; i >= 0; --i) {
        ss << std::setw(2) << ((num >> (i * 8)) & 0xFF);
        if (i == 6) ss << "-";
    }

    return ss.str();
}

int main(){
    for(int i = 0; i < 20; ++i){
        std::cout << uuid() << std::endl;
    }
    return 0;
}

输出结果:

bash 复制代码
4b0dfed8-c0b2-1432-0000-000000000001
d8f17d63-5976-daea-0000-000000000002
e48feafa-4353-e05c-0000-000000000003
3eda3cab-7f53-bd7e-0000-000000000004
d50c80cd-4738-d73d-0000-000000000005
f26f8f65-7b66-1492-0000-000000000006
a708727f-51d7-85ee-0000-000000000007
ca8caead-223d-ce69-0000-000000000008
07ebec5f-9a70-94fa-0000-000000000009
e657db76-cb5a-08a2-0000-00000000000a
e5105c92-0ccd-a9ff-0000-00000000000b
b93b9fda-315a-e177-0000-00000000000c
873dc6a0-0f77-2df3-0000-00000000000d
c81ec758-6104-9d36-0000-00000000000e
5d7f240e-92b8-9d02-0000-00000000000f
ee1bfde5-6d40-d539-0000-000000000010
734d0028-f3e5-e448-0000-000000000011
1f870f3b-b430-08c8-0000-000000000012
946df344-3de2-bacb-0000-000000000013
2579d0a0-dcd6-550e-0000-000000000014

文件基础操作类

我们需要实现一些文件的基础操作:

a. 文件是否存在判断

b. 文件大小获取

c. 文件读 / 写

d. 文件创建 / 删除

e. 目录创建 / 删除

cpp 复制代码
class FileHelper{
public:
    FileHelper(const std::string& filename):_filename(filename){}

    bool exists(){
        struct stat st;
        return (stat(_filename.c_str(),&st) == 0);
    }

    size_t size(){
        struct stat st;
        int ret = stat(_filename.c_str(),&st);
        if(ret < 0){
            return 0;
        }else{
            return st.st_size;
        }
    }

    bool read(char *body, size_t offset, size_t len){
        // 1.打开文件 
        std::ifstream ifs(_filename,std::ios::binary | std::ios::in);
        if(ifs.is_open() == false){
            ELOG("%s 文件打开失败!",_filename.c_str());
            return false;
        }
        // 2.跳转文件指定位置
        ifs.seekg(offset,std::ios::beg);
        // 3.读取文件数据
        ifs.read(body,len);
        if(ifs.good() == false){
            ELOG("%s 文件读取失败!!",_filename.c_str());
            ifs.close();
            return false;
        }
        // 4.关闭文件
        ifs.close();
        return true;
    }

    bool read(std::string &body){
        // 获取文件大小,根据文件大小调整body空间
        size_t fsize = this->size();
        body.resize(fsize);
        return read(&body[0],0,fsize);
    }

    bool write(const char* body, size_t offset,size_t len){
        // 1.打开文件 
        std::fstream fs(_filename,std::ios::binary | std::ios::in | std::ios::out);
        if(fs.is_open() == false){
            ELOG("%s 文件打开失败!",_filename.c_str());
            return false;
        }
        // 2.跳转文件指定位置
        fs.seekp(offset,std::ios::beg);
        // 3.写入数据
        fs.write(body,len);
        if(fs.good() == false){
            ELOG("%s 写入文件数据失败!!",_filename.c_str());
            fs.close();
            return false;
        }
        // 4.关闭文件
        fs.close();
        return true;
    }

    bool write(const std::string &body){
        return write(&body[0],0,body.size());
    }

    bool rename(const std::string &nname){
        return (::rename(_filename.c_str(),nname.c_str()) == 0);
    }

    static std::string parentDirectory(const std::string &filename){
        size_t pos = filename.find_last_of("/");
        if(pos == std::string::npos){
            return "./";
        }
        std::string path = filename.substr(0,pos + 1);
        return path;
    }

    static bool createFile(const std::string &filename){
        std::fstream ofs(filename,std::ios::binary | std::ios::out);
        if(ofs.is_open() == false){
            ELOG("%s 文件打开失败!",filename.c_str());
            return false;
        }
        ofs.close();
        return true;
    }
    
    static bool removeFile(const std::string &filename){
        return (::remove(filename.c_str()) == 0);
    }

    static bool createDirectory(const std::string &path){
        size_t pos,idx = 0,len = path.size();
        while(idx < len){
            pos = path.find("/",idx);
            if(pos == std::string::npos){
                return (mkdir(path.c_str(),0775) == 0);
            }
            std::string subpath = path.substr(0,pos);
            int ret = mkdir(subpath.c_str(),0775);
            if(ret != 0 && errno != EEXIST){
                ELOG("创建目录 %s 失败:%s",subpath.c_str(),strerror(errno));
                return false;
            }
            idx = pos + 1;
        }
        return true;
    }

    static bool removeDirectory(const std::string &path){
        std::string cmd = "rm -rf " +  path;
        return (system(cmd.c_str()) != -1);
    }
private:
    std::string _filename;
};

编写Proto文件

在自研轻量 MQ 项目开发中,采用 Protobuf3 定义统一数据协议,规范消息序列化格式,适配网络传输与本地持久化场景,保证多模块数据交互统一。

协议文件指定 proto3 语法与专属包名 Fy_mq,隔离命名空间,避免命名冲突。

结合消息队列核心能力,定义两组枚举类型。交换机类型包含直连、广播、主题三种主流路由模式,并设置默认未知类型做异常兜底。投递模式区分非持久化与持久化消息,用来控制磁盘落地策略,保障消息可靠性。

通过 BasicProperties 结构体封装消息基础属性,包含唯一消息ID、投递模式与路由键,为交换机转发、消息管理提供核心参数。

顶层设计嵌套式消息结构,内部 Payload 承载消息属性、内容与有效性标识。其中有效性字段采用字符串格式,依托天然优势保证跨平台解析兼容。额外增加文件偏移与消息长度字段,适配持久化文件读写,实现快速定位与数据校验。

该协议结构分层清晰,兼顾业务投递与存储底层需求,借助 protoc 编译生成 C++ 代码,可快速完成消息序列化与反序列化,简化开发,提升项目整体规范性。

protobuf 复制代码
syntax = "proto3";
package Fy_mq;

enum ExchangeType {
    UNKNOWTYPE = 0;
    DIRECT = 1;
    FANOUT = 2;
    TOPIC = 3;
};

enum DeliverMode{
    UNKNOWMODE = 0;
    UNDURABLE = 1;
    DURABLE = 2;
};

message BasicProperties{
    string id = 1;
    DeliverMode deliver_mode = 2;
    string routing_key = 3;
};

message Message{
    message Payload{
        BasicProperties properties = 1;
        string body = 2;
        string valid = 3;
    };
    Payload payload = 1;
    uint32 offset = 2;
    uint32 length = 3;
};

交换机数据管理

交换机模块是自研轻量 MQ 中路由分发的核心核心模块 ,整体采用三层分层设计 :交换机实体结构、SQLite 持久化映射层、线程安全内存管理层。模块依托 Protobuf 枚举类型 规范路由类型,结合本地数据库落地存储,实现交换机实例的内存高效管理、断电持久化恢复、多线程并发安全三大核心能力。

交换机实体结构封装

通过结构体抽象交换机完整属性,统一承载运行时核心数据,包含基础属性扩展参数两大模块。

  • 基础属性:交换机名称、路由类型、持久化标记、自动删除标记
  • 扩展参数:采用 google::protobuf::Map 键值对容器存储,适配跨平台数据交互需求

提供参数序列化与反序列化 接口,将扩展参数按照 key=val&key=val 规则拼接与解析,同时对非法格式字符串做日志告警与容错跳过,避免非法数据导致程序异常。

cpp 复制代码
struct Exchange{
    using ptr = std::shared_ptr<Exchange>;
    //1.交换机名称
    std::string name;
    //2.交换机类型
    ExchangeType type;
    //3.交换机持久化标记
    bool durable;
    //4.是否自动删除标记
    bool auto_delete;
    //5.其他参数
    google::protobuf::Map<std::string,std::string> args;

    Exchange(){}
    Exchange(const std::string &ename,
        ExchangeType etype,
        bool edurable,
        bool eauto_delete,
        const google::protobuf::Map<std::string,std::string> &eargs)
        :name(ename),type(etype),durable(edurable),auto_delete(eauto_delete),args(eargs){}

    //args存储键值对,在存储数据库的时候,会组织一个格式字符串进行存储 key=val&key=val....
    //内部解析str_args字符串,将内容存储到成员中
    void SetArgs(const std::string &str_args){
        std::vector<std::string> sub_args;
        StrHelper::split(str_args,"&",sub_args);
        for(auto &str : sub_args){
            size_t pos = str.find("=");
            if( pos == std::string::npos){
                ELOG("交换机解析格式非法:%s",str.c_str());
                continue;//跳过非法格式
            }
            std::string key = str.substr(0,pos);
            std::string value = str.substr(pos+1);
            args[key] = value;
        }
    }

    std::string getArgs(){
        std::string result;
        for(auto &it:args){
            result += it.first + "=" + it.second + "&";
        }
        return result;
    }
};

交换机持久化映射层

ExchangeMapper 作为数据库交互专属层 ,完全隔离底层 SQL 操作,专注实现交换机数据的落地存储与重启恢复

构造时自动完成目录检测创建、数据库打开、数据表初始化,保障环境可用性;封装建表、删表、插入、删除、数据恢复全套操作。

  • 数据表以交换机名称为主键,保证实例全局唯一
  • 数值类型字段统一转为整型入库,适配 SQLite 弱类型存储特性
  • 查询回调中增加空指针边界判断,杜绝数据库 NULL 字段引发崩溃
  • 服务启动时执行 recovery 方法,自动加载磁盘持久化数据至内存,实现数据持久化闭环
cpp 复制代码
using ExchangeMap = std::unordered_map<std::string,Exchange::ptr>;//内存管理用
class ExchangeMapper{
public:
    ExchangeMapper(const std::string &dbfile):_sql_helper(dbfile){
        std::string path = FileHelper::parentDirectory(dbfile);
        FileHelper::createDirectory(path);
        assert(_sql_helper.open());
        createTable();
    }

    void createTable(){
        #define CREATE_TABLE "create table if not exists exchange_table(\
            name varchar(32) primary key,\
            type int,\
            durable int,\
            auto_delete int,\
            args varchar(128));"
        bool ret = _sql_helper.exec(CREATE_TABLE,nullptr,nullptr);
        if(ret == false){
            DLOG("创建交换机数据库表失败!");
            abort();
        }
    }
    
    void removeTable(){
        #define DROP_TABLE "drop table if exists exchange_table;"
        bool ret = _sql_helper.exec(DROP_TABLE,nullptr,nullptr);
        if(ret == false){
            DLOG("删除交换机数据库表失败!");
            abort();
        }
    }

    bool insert(Exchange::ptr &exp){
        std::stringstream ss;
        ss << "insert into exchange_table (name, type, durable, auto_delete, args) values(";
        ss << "'" << exp->name << "',";
        ss << "'" << (int)exp->type << "',";
        ss << "'" << (int)exp->durable << "',";
        ss << "'" << (int)exp->auto_delete << "',";
        ss << "'" << exp->getArgs() << "');";
        return _sql_helper.exec(ss.str(),nullptr,nullptr);
    }

    void remove(const std::string &name){
        std::stringstream ss;
        ss << "delete from exchange_table where name = ";
        ss << "'" << name << "';";
        _sql_helper.exec(ss.str(),nullptr,nullptr);
    }

    ExchangeMap recovery(){
        ExchangeMap result;
        std::string sql = "select name,type,durable,auto_delete,args from exchange_table;";
        _sql_helper.exec(sql,selectCallback,&result);
        return result;
    }
private:
    static int selectCallback(void *arg,int numcol,char** row,char** fields){
        if (!row[0] || !row[1] || !row[2] || !row[3]) return 0;

        ExchangeMap *result = (ExchangeMap*)arg;
        auto exp = std::make_shared<Exchange>();
        exp->name = row[0];
        exp->type = (Fy_mq::ExchangeType)std::stoi(row[1]);
        exp->durable = (bool)std::stoi(row[2]);
        exp->auto_delete = (bool)std::stoi(row[3]);
        if(row[4]) exp->SetArgs(row[4]);
        result->insert(std::make_pair(exp->name,exp));
        return 0;
    }
private:
    SqliteHelper _sql_helper;
};

交换机内存管理层

ExchangeManager业务上层直接调用的管理入口 ,核心负责:多线程安全控制、内存容器管理、内存与数据库联动

  1. 并发安全 :所有增删查改接口全部加 std::unique_lock 锁保护,杜绝多线程资源竞争问题
  2. 幂等声明 :声明交换机时优先检索内存容器,已存在直接返回,避免重复创建
  3. 持久化联动 :仅对 durable 标记为 true 的交换机执行数据库写入与删除,区分临时/持久化实例
  4. 资源管理 :基于 std::shared_ptr 智能指针托管生命周期,内存自动回收
  5. 调试适配clear 方法为开发调试定制,支持一键清空内存+删除数据表,方便本地测试
cpp 复制代码
class ExchangeManager{
public:
    using ptr = std::shared_ptr<ExchangeManager>;

    ExchangeManager(const std::string &dbfile): _mapper(dbfile){
        _exchanges = _mapper.recovery();
    }

    //声明交换机
    bool declareExchange(const std::string&name,
        ExchangeType type,bool durable,bool auto_delete,
        const google::protobuf::Map<std::string,std::string> &args){
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if(it != _exchanges.end()){
                //已经存在
                return true;
            }
            auto exp = std::make_shared<Exchange>(name,type,durable,auto_delete,args);
            if(durable == true){
                bool ret = _mapper.insert(exp);
                if(ret == false) return false;
            }
            _exchanges.insert(std::make_pair(name,exp));
            return true;
    }
    
    //删除交换机
    void deleteExchange(const std::string &name){
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _exchanges.find(name);
        if(it == _exchanges.end()){
            return ;
        }
        if(it->second->durable == true) _mapper.remove(name);
        _exchanges.erase(name);
    }

    //获取指定交换机
    Exchange::ptr selectExchange(const std::string &name){
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _exchanges.find(name);
        if(it == _exchanges.end()){
            return Exchange::ptr();
        }
        return it->second;
    }
    
    size_t size(){
        std::unique_lock<std::mutex> lock(_mutex);
        return _exchanges.size();
    }

    //清除所有交换机
    void clear(){
        std::unique_lock<std::mutex> lock(_mutex);
        _mapper.removeTable();
        _exchanges.clear();
    }
private:
    std::mutex _mutex;
    ExchangeMapper _mapper;
    ExchangeMap _exchanges;
};

测试代码

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

Fy_mq::ExchangeManager::ptr emp;

class ExchangeTest : public testing::Environment{
public:
    virtual void SetUp() override{
        emp = std::make_shared<Fy_mq::ExchangeManager>("./data/meta.db");
    }
    virtual void TearDown() override{
        emp->clear();
        DLOG("最后的清理!!\n");
    }
};

TEST(exchange_test, insert_test) {
    google::protobuf::Map<std::string, std::string> map;

    map["k1"] = "v1";
    map["k2"] = "v2";

    emp->declareExchange("exchange1", Fy_mq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange2", Fy_mq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange3", Fy_mq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange4", Fy_mq::ExchangeType::DIRECT, true, false, map);
    ASSERT_EQ(emp->size(), 4);
}

TEST(exchange_test, remove_test) {
    emp->deleteExchange("exchange3");
    Fy_mq::Exchange::ptr exp = emp->selectExchange("exchange3");
    ASSERT_EQ(exp.get(), nullptr);
    ASSERT_EQ(emp->exists("exchange3"), false);
}

TEST(exchange_test, select_test) {
    ASSERT_EQ(emp->exists("exchange1"), true);
    ASSERT_EQ(emp->exists("exchange2"), true);
    ASSERT_EQ(emp->exists("exchange3"), false);
    ASSERT_EQ(emp->exists("exchange4"), true);
    Fy_mq::Exchange::ptr exp = emp->selectExchange("exchange2");
    ASSERT_NE(exp.get(), nullptr);
    ASSERT_EQ(exp->name, "exchange2");
    ASSERT_EQ(exp->durable, true);
    ASSERT_EQ(exp->auto_delete, false);
    ASSERT_EQ(exp->type, Fy_mq::ExchangeType::DIRECT);
    
    ASSERT_EQ(exp->args["k1"], "v1");
    ASSERT_EQ(exp->args["k2"], "v2");
}

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

运行结果:

模块整体设计亮点

  • 职责解耦:数据结构、持久化操作、业务管理三层完全拆分,低耦合易维护
  • 容错性强:参数解析、数据库查询、SQL 执行全链路异常拦截与日志提示
  • 工程化规范:智能指针托管、互斥锁并发保护、枚举约束业务类型,适配后端服务开发标准
  • 可扩展性强:扩展参数容器预留自定义配置能力,可后期快速拓展交换机附加功能
相关推荐
.柒宇.3 小时前
RabbitMQ入门教程
分布式·rabbitmq
代码漫谈4 小时前
RabbitMQ 单节点部署指南
分布式·消息队列·rabbitmq
aLTttY5 小时前
Spring Boot + Redis 实战分布式锁:从入门到精通
spring boot·redis·分布式
weixin_419658315 小时前
RabbitMQ 应用问题
java·分布式·中间件·rabbitmq
2301_815279525 小时前
RabbitMQ - 在微服务架构中的落地实践:消息推送 / 解耦 / 削峰填谷
微服务·架构·rabbitmq
希望永不加班5 小时前
SpringBoot 整合 RabbitMQ 入门
java·spring boot·后端·rabbitmq·java-rabbitmq
爱艺江河5 小时前
HarmonyOS智慧风控:基于分布式架构的安全与创新实践
分布式·架构·harmonyos
juniperhan5 小时前
Flink 系列第18篇:Flink 动态表、连续查询与 Changelog 机制
java·大数据·数据仓库·分布式·flink
juniperhan5 小时前
Flink 系列第19篇:深入理解 Flink SQL 的时间语义与时区处理:从原理到实战
java·大数据·数据仓库·分布式·sql·flink