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

路由匹配模块

一、模块概述

路由模块是自研消息队列的核心基础模块,负责路由键合法性校验、绑定键合法性校验、交换机消息路由匹配 三大核心能力。

适配直连(DIRECT)、广播(FANOUT)、主题(TOPIC)三种交换机类型,严格遵循 RabbitMQ 路由语义规范,同时做绑定键通配符语法合法性强校验,杜绝非法通配符格式,保障路由匹配逻辑严谨、无消息错配、漏配。

模块核心能力:

  1. 路由键合法性校验:仅允许大小写字母、数字、下划线、小数点;
  2. 绑定键合法性校验:在路由键基础上新增 * # 通配符,并做通配符语法规则校验;
  3. 多类型交换机路由匹配:DIRECT 精确匹配、FANOUT 全匹配、TOPIC 通配符层级匹配;
  4. 采用一维滚动 DP 实现 Topic 匹配,空间复杂度更低、执行效率高,适配高并发消息投递场景。

二、核心语法规范

1. 路由键 RoutingKey 合法规则

仅允许字符:a-z、A-Z、0-9、.、_

不允许任何特殊符号、空格、通配符。

2. 绑定键 BindingKey 合法规则

合法字符:a-z、A-Z、0-9、.、_、*、#

额外语法约束:

  1. * # 必须独立作为一个层级,不能和普通字符混杂(如 mu*sic 非法);
  2. 禁止通配符连续非法组合:# 后不能接 #/** 后不能接 #
  3. 借助合法性校验前置拦截非法格式,路由匹配层无需做语法容错。

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 编写全覆盖测试用例,覆盖三大场景:

  1. 路由键合法/非法字符校验;
  2. 绑定键字符合法、通配符语法非法校验;
  3. 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.

所有用例全部通过

消费者管理模块

一、模块概述

消费者管理模块 是自研消息队列服务端的核心组件,主要负责消费者生命周期管理、队列与消费者关联维护、多消费者负载均衡调度,为消息可靠、均匀、高效投递提供底层支撑。

模块采用三层架构设计:

  1. Consumer:消费者实体,封装标识、队列、应答模式、回调函数;
  2. QueueConsumer:队列级消费者管理器,实现单队列下多消费者的增删、轮询选择;
  3. 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.
相关推荐
Albert Edison5 小时前
【RabbitMQ】发布确认模式(使用案例)
分布式·rabbitmq·ruby
EXnf1SbYK6 小时前
Redis分布式锁进阶第十二篇:全系列终极兜底复盘 + 锁架构巡检落地 + 线上零事故收尾方案
redis·分布式·架构
EXnf1SbYK7 小时前
Redis分布式锁进阶第八篇:锁超时乱序深度踩坑 + 看门狗失效真实溯源 + 业务长耗时标准化兜底方案
数据库·redis·分布式
EXnf1SbYK7 小时前
Redis分布式锁进阶第十一篇
数据库·redis·分布式
biyezuopinvip8 小时前
分布式风电场低电压穿越故障建模与仿真
分布式·matlab·毕业设计·毕业论文·分布式风电场·低电压穿越故障·建模与仿真
苍煜8 小时前
SpringBoot单体应用到分布式下的数据库锁、事务、Redis事务、分布式锁、分布式事务协调
数据库·spring boot·分布式
fengxin_rou8 小时前
黑马点评项目万字总结:从redis基础到实战应用详解
java·开发语言·分布式·后端·黑马点评
小江的记录本18 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
身如柳絮随风扬1 天前
多数据源切换实战:从业务场景到3种实现方案全解析
java·分布式·微服务