coordinate-broadcast 架构设计

1. 系统概述

coordinate-broadcast 是整个 Coordinate 消息系统中负责消息路由和分发的核心组件,基于 rumqttd 进行二次开发,扮演着 MQTT 消息代理(Broker)的角色。系统包含两个独立的 Broker 实例:

  • Broadcast Server(MQTT v5 Extended):内部服务器通信,使用纯 TCP 连接,实现完整 MQTT 协议并扩展 AddSubscribe/RemoveSubscribe 用于动态订阅管理
  • WebSocket Server(MQTT v5 Lite):外部客户端接入,使用 WebSocket 传输,仅支持连接管理相关消息

作为分布式消息架构的核心枢纽,coordinate-broadcast 接收来自 coordinate-server 的事件发布请求,并通过 MQTT 协议将消息推送至各个客户端(coordinate-connector 或 coordinate-sdk 接入的客户端)。

从功能定位角度来看,coordinate-broadcast 主要承担三类职责:第一是作为消息路由中枢,接收发布者的消息并根据主题过滤器进行智能路由;第二是作为连接管理器,管理大量并发 MQTT 客户端的连接生命周期;第三是作为消息存储器,通过内存段(Segment)结构维护消息历史,支持消息回溯和离线推送。系统设计遵循高性能、低延迟的原则,采用 Actor 架构模式实现消息处理与连接管理的解耦,整体架构能够支撑十万量级的并发连接。

2. 系统架构

2.1 整体架构图

flowchart TB subgraph coordinate-server["coordinate-server"] direction LR Server["server"] end subgraph coordinate-broadcast["coordinate-broadcast"] subgraph broadcast["Broadcast Server (MQTT v5 Extended) - 内部 TCP"] direction TB BServer["Server"] end subgraph router["Router - 核心路由"] direction TB Router["Router"] Subs["订阅映射"] Sched["调度器"] end subgraph link["Link - 网络I/O"] direction TB Link["Link"] end subgraph segments["Segments - 消息存储"] direction TB Seg["分段存储"] end subgraph ws["WebSocket Server (MQTT v5 Lite) - 外部客户端"] direction TB WSServer["Server"] end end subgraph clients["MQTT Clients"] direction LR Connector["connector"] SDK["coordinate-sdk"] end Server -->|"MQTT v5 Extended"| BServer BServer --> Router Router <--> Link Router <--> Seg Link --> WSServer WSServer -->|"MQTT over WebSocket"| clients

2.2 模块职责划分

coordinate-broadcast 采用模块化的架构设计,每个模块承担独立的职责,通过清晰定义的接口进行模块间交互。这种设计使得各模块可以独立演进,同时便于测试和维护。系统主要包含以下六个核心模块:

server 模块负责网络监听和连接接受,是系统对外的入口点。它启动 TCP 监听器(支持 WebSocket 和纯 TCP 两种模式),接受客户端连接并进行 TLS 握手处理。在接受连接后,server 模块负责解析 MQTT Connect 包,验证客户端身份,并创建对应的 Link 实例来处理后续通信。该模块还负责 WebSocket 子协议协商,确保使用 "mqtt" 子协议进行通信。

protocol 模块实现了 MQTT 协议的解析与序列化功能,是系统进行协议级通信的基础。该模块实现了两个协议版本,用于不同的场景:

  • v5 Extended :用于内部服务器通信(broadcast 模块),实现完整的 MQTT 协议并扩展 AddSubscribe/RemoveSubscribe 用于动态订阅管理
  • v5 Lite :用于外部 WebSocket 客户端,仅支持连接管理相关消息( Connect/ConnAck/Ping/Disconnect ) 协议实现采用 trait 抽象设计,提供了良好的扩展性,能够方便地添加新协议版本支持。

router 模块是系统的核心消息路由引擎,负责管理所有订阅关系、执行消息路由、调度消费者并维护连接状态。它维护订阅映射表(Subscription Map),将主题过滤器映射到对应的连接集合;当收到发布消息时,router 根据订阅关系查找订阅者并将消息推送给目标连接。该模块还实现了共享订阅(Shared Subscription)功能,支持消息在订阅同一主题的多个消费者之间进行负载均衡分发。

link 模块负责网络 I/O 操作和协议编解码,是连接生命周期管理的核心实现。每个 Link 实例对应一个 MQTT 客户端连接,负责处理入站和出站数据流。Link 模块使用异步通道(Channel)与 Router 进行通信,实现消息的接收和发送。该模块还负责处理 QoS(服务质量)确认、消息重传等可靠性机制。

broadcast 模块实现了专门的广播服务器功能,用于将消息分发至多个订阅者。与普通的 pub/sub 不同,广播服务器针对大规模分发场景进行了优化,能够高效地将同一条消息推送给数千个订阅者。该模块通常与 coordinate-server 配合使用,处理系统级别的事件广播。

segments 模块实现了消息的持久化存储功能,使用 CommitLog 结构管理消息历史。每个 Segment 是一个内存映射文件,包含固定大小的消息块。当消息进入时,会追加到当前的 Segment;当 Segment 满时,会创建新的 Segment 并对旧的进行归档。这种设计使得系统能够支持消息回溯和离线推送,同时通过定期清理旧 Segment 来控制内存使用。

2.3 数据流设计

系统的核心数据流遵循"接收→路由→分发"的三阶段设计,每一阶段都由独立的异步任务处理,保证系统的高吞吐量。以下是消息从接收到分发的完整数据流程:

在第一阶段连接建立时,server 模块接受客户端 TCP 连接,创建 Network 层进行底层 I/O 处理。客户端发送 MQTT Connect 包后,系统解析连接参数并验证身份认证信息。认证通过后,创建 RemoteLink 实例并将连接信息注册到 Router。Router 为该连接分配唯一的 ConnectionId,并创建对应的入站和出站队列用于消息缓冲。

在第二阶段消息接收时,客户端通过 Link 发送 Publish 数据包。Link 将数据包放入连接对应的入站队列,然后通知 Router 进行处理。Router 从队列中取出数据包,首先进行 QoS 确认处理(对于 QoS 1 需要回复 PubAck)。然后将消息追加到 CommitLog 进行持久化存储。最后根据消息主题查找订阅映射表,确定目标订阅者并生成数据请求。

在第三阶段消息分发时,Router 将数据请求加入调度队列。调度器(Scheduler)根据连接状态和背压情况决定分发时机。当连接可以接收新数据时,调度器从队列中取出请求并调用 Link 的发送方法。Link 将数据包序列化后通过 TCP 套接字发送给客户端。如果连接背压(Buffer Full),调度器会暂停向该连接分发数据,等待客户端确认接收完成后继续。

3. 核心组件设计

3.1 Broker 核心类

Broker 是 coordinate-broadcast 的主控制类,负责协调各模块启动和管理全局状态。以下是 Broker 的核心设计:

rust 复制代码
pub struct Broker {
    pub config: Config,
    pub router_tx: Sender<(ConnectionId, Event)>,
}

impl Broker {
    pub async fn start(&mut self) -> Result<(), Error> {
        // 1. 启动广播服务器
        if let Some(config) = self.config.broadcast.clone() {
            tokio::spawn(async move {
                let mut broadcast_server = BroadcastServer::new(config, router_tx, V0);
                broadcast_server.start().await.unwrap();
            });
        }

        // 2. 启动 WebSocket 服务器(MQTT over WebSocket)
        if let Some(mut config) = self.config.ws.clone() {
            let mut server = Server::new(config, self.router_tx.clone(), V5);
            server.start(LinkType::Websocket).await;
        }
    }
}

Broker 的启动流程,首先根据配置启动广播服务器(Broadcast Server),然后启动 WebSocket 服务器(WebSocket Server)。

3.2 Router 核心类

Router 是系统的核心消息路由引擎,采用 Actor 模式设计,通过事件驱动的方式处理各类消息。以下是 Router 的核心数据结构:

rust 复制代码
pub struct Router {
    pub id: RouterId,
    pub config: RouterConfig,
    pub subscription_map: HashMap<Topic, HashSet<ConnectionId>>,
    pub shared_subscriptions: HashMap<String, SharedGroup>,
    pub scheduler: Scheduler,
    pub cache: RingBuffer<IncomingPacket>,
}

impl Router {
    pub fn events(&mut self, id: ConnectionId, data: Event) {
        match data {
            Event::Connect { connection, incoming, outgoing } => {
                self.handle_new_connection(connection, incoming, outgoing);
            }
            Event::DeviceData => self.handle_device_payload(id),
            Event::Disconnect => self.handle_disconnection(id, None),
            Event::PublishWill((client_id, _)) => self.handle_last_will(client_id),
        }
    }
}

Router 维护几个关键的数据结构:subscription_map 用于将主题过滤器映射到订阅者连接集合;shared_subscriptions 用于管理共享订阅组,支持 RoundRobin、Random 和 Sticky 三种分发策略;scheduler 负责调度消息分发任务,实现背压控制;cache 用于缓存入站数据包,避免频繁分配。

3.3 连接管理

连接管理是系统高并发的关键,采用了以下设计来实现高效的连接处理:

rust 复制代码
pub struct ConnectionSettings {
    pub connection_timeout_ms: u16,       // 连接超时时间
    pub max_payload_size: usize,          // 最大 payload 大小
    pub max_inflight_count: usize,         // 最大飞行中消息数
    pub auth: Option<HashMap<String, String>>,  // 内部认证配置
    pub external_auth: Option<AuthHandler>,      // 外部认证处理器
    pub dynamic_filters: bool,             // 是否支持动态过滤器
}

连接设置提供了灵活的认证机制,支持内部认证(基于配置文件的简单认证)和外部认证(通过异步回调函数进行自定义认证)。max_inflight_count 用于控制每个连接飞行中消息的数量,实现背压控制,防止单个连接占用过多资源导致其他连接延迟。

4. 关键技术实现

4.1 MQTT 协议实现

系统实现了两个协议版本,用于不同的场景:

  • v5 Extended :用于内部服务器通信,实现完整的 MQTT 协议,包含 Connect、ConnAck、Publish、PubAck、Subscribe、SubAck、Unsubscribe、UnsubAck、PingReq、PingResp、Disconnect,并扩展支持 AddSubscribe(15)、RemoveSubscribe(16)消息类型用于动态订阅管理
  • v5 Lite :用于外部 WebSocket 客户端,仅支持连接管理相关消息( Connect/ConnAck/Ping/Disconnect ),不支持 Publish/Subscribe

通过统一的 Packet 类型封装所有消息类型:

rust 复制代码
pub trait Protocol {
    fn read_mut(&mut self, stream: &mut BytesMut, max_size: usize) -> Result<Packet, Error>;
    fn write(&self, packet: Packet, write: &mut BytesMut) -> Result<usize, Error>;
}

pub enum Packet {
    Connect(Connect, Option<ConnectProperties>, Option<LastWill>, ...),
    ConnAck(ConnAck, Option<ConnAckProperties>),
    Publish(Publish, Option<PublishProperties>),
    PubAck(PubAck, Option<PubAckProperties>),
    Subscribe(Subscribe, Option<SubscribeProperties>),
    SubAck(SubAck, Option<SubAckProperties>),
    // ... 其他包类型
}

协议实现的关键设计要点包括:第一,使用统一的 Packet 类型封装所有 MQTT 消息类型,通过 serde 库实现序列化;第二,通过 Protocol trait 抽象协议实现,便于扩展新版本;第三,实现了 QoS 机制,支持 AtMostOnce(0)和 AtLeastOnce(1)两种服务质量级别。

v5 Lite 是 MQTT v5 的子集,仅保留连接管理相关消息。

4.2 WebSocket 传输

系统支持 MQTT over WebSocket,允许浏览器或 Web 环境通过 WebSocket 协议接入。以下是 WebSocket 握手的关键实现:

rust 复制代码
struct WSCallback;
impl Callback for WSCallback {
    fn on_request(self, request: &Request, mut response: Response) -> Result<Response, ErrorResponse> {
        // 协商 mqtt 子协议
        if request.headers().get("sec-websocket-protocol").is_some() {
            response.headers_mut().insert("sec-websocket-protocol", HeaderValue::from_static("mqtt"));
        }
        Ok(response)
    }
}

// WebSocket 连接处理
let stream = accept_hdr_async(network, WSCallback).await?;
let stream = Box::new(WsStream::new(s));
task::spawn(remote(config, router_tx, stream, protocol, will_handlers));

WebSocket 传输的关键设计是在握手阶段协商 "mqtt" 子协议,确保使用标准化的 MQTT 消息格式进行后续通信。这种设计充分利用了 WebSocket 的全双工通信能力和防火墙穿透能力,同时保持与原生 MQTT 的兼容性。

4.3 发布/订阅机制

发布/订阅是系统的核心功能,实现了灵活的主题匹配和高效的消息分发:

rust 复制代码
pub struct Filter {
    pub path: String,           // 主题路径
    pub qos: QoS,              // QoS 级别
    pub nolocal: bool,         // 不转发本地发布
    pub preserve_retain: bool,
    pub retain_forward_rule: RetainForwardRule,
}

impl Router {
    fn prepare_filter(&mut self, id: ConnectionId, cursor: Offset, filter_idx: FilterIdx, 
                      filter: &protocol::Filter, group: Option<String>, subscription_id: Option<usize>) {
        // 1. 添加到订阅映射
        match self.subscription_map.get_mut(&filter.path) {
            Some(connections) => { connections.insert(id); }
            None => {
                let mut connections = HashSet::new();
                connections.insert(id);
                self.subscription_map.insert(filter.path.clone(), connections);
            }
        }

        // 2. 处理共享订阅
        if let Some(group_name) = &group {
            let shared_group = self.shared_subscriptions
                .entry(group_name.to_string())
                .or_insert(SharedGroup::new(cursor, self.config.shared_subscriptions_strategy.clone()));
            shared_group.add_client(client_id);
        }

        self.scheduler.track(id, request);
    }
}

订阅机制支持精确匹配和通配符匹配两种模式:单层通配符(+)匹配单层路径,多层通配符(#)匹配多层路径。共享订阅允许多个消费者订阅同一主题,系统通过策略模式实现消息分发,支持 RoundRobin(轮询)、Random(随机)和 Sticky(粘性)三种策略。

4.4 消息持久化

消息持久化使用分段日志(CommitLog)结构实现,平衡性能和存储开销:

rust 复制代码
pub struct Segment {
    pub id: u64,
    pub path: PathBuf,
    pub file: File,
    pub size: usize,
    pub cursor: u64,
}

impl Segment {
    pub fn append(&mut self, data: &[u8]) -> Result<Offset, Error> {
        let offset = self.cursor;
        self.file.write_all(data)?;
        self.cursor += data.len() as u64;
        Ok((self.id, offset))
    }
}

分段存储的设计要点包括:每个 Segment 有固定的最大大小(默认 1GB),当 Segment 满时创建新 Segment;消息以追加模式写入,保证顺序性;通过定期清理旧 Segment 来控制磁盘空间使用。消息持久化支持离线消息和消息回溯,是 MQTT 消息代理的核心功能之一。

5. 配置设计

5.1 配置文件结构

系统通过 mqtt.toml 配置文件进行配置,支持灵活的系统调优。以下是完整的配置结构:

toml 复制代码
# 路由器配置
[router]
id = 0
max_connections = 100010
max_outgoing_packet_count = 2000
max_segment_size = 1048576000    # 1GB
max_segment_count = 100

# WebSocket 服务器配置
[ws]
name = "ws"
listen = "0.0.0.0:8000"
next_connection_delay_ms = 1
[ws.connections]
connection_timeout_ms = 60000
max_payload_size = 20480
max_inflight_count = 100
# auth = { broadcast = "password" }

# 广播服务器配置
[broadcast]
name = "broadcast"
listen = "0.0.0.0:21884"
next_connection_delay_ms = 1
[broadcast.connections]
connection_timeout_ms = 60000
max_payload_size = 20480
max_inflight_count = 100

# Prometheus 指标
[prometheus]
listen = "127.0.0.1:9043"
interval = 1

# 指标推送配置
[metrics]
[metrics.alerts]
push_interval = 10
[metrics.meters]
push_interval = 2

5.2 配置项说明

配置项按照功能分为以下几个组:

路由器配置控制消息路由的核心行为:max_connections 限制最大连接数,防止资源耗尽;max_outgoing_packet_count 控制每个连接出队缓冲大小;max_segment_size 和 max_segment_count 控制消息存储的磁盘空间。

服务器配置控制网络监听行为:listen 指定监听地址和端口;next_connection_delay_ms 控制新连接的接受速率,防止瞬时连接风暴;connection_timeout_ms 控制空闲连接的超时时间。

连接配置控制单个连接的行为:max_payload_size 限制单条消息的最大大小;max_inflight_count 控制飞行中消息的数量,实现背压控制;auth 用于配置简单的用户名密码认证。

6. 系统交互设计

6.1 与 coordinate-server 的交互

coordinate-server 通过 coordinate-connector 库使用 MQTT v5 Extended 连接到 Broadcast Server,发布系统事件并接收订阅状态更新:

rust 复制代码
// coordinate-server 侧的事件发布
pub async fn publish(topic: String, event: Event) -> Result<(), AppError> {
    if let Some(senders) = BROADCAST.get() {
        let idx = CLIENT_INDEX.fetch_add(1, Ordering::Relaxed) % senders.len();
        let tx = &senders[idx];
        tx.send(BroadcastEvent::Publish(topic, event)).await?;
    }
    Ok(())
}

coordinate-server 维护一个连接池(默认4个连接),通过 round-robin 方式分发发布请求,实现负载均衡。每个连接独立维护 MQTT 会话,支持 Clean Session 和持久会话两种模式。

6.2 消息流总结

系统中事件流转的完整路径如下:coordinate-server 产生业务事件,通过 connector 客户端发布到 coordinate-broadcast;broadcast 根据订阅关系将消息路由到订阅该主题的所有客户端;客户端接收消息后触发本地业务处理逻辑。这种架构实现了组件间的松耦合,便于横向扩展。

7. 设计模式总结

7.1 架构模式

系统采用以下架构模式实现高性能和高可用性:

Actor 模式:Router 作为核心 Actor,通过 Channel 与连接通信。所有的状态修改都在 Router 单线程中处理,避免了锁竞争和数据竞争。

事件驱动模式:系统基于 tokio 的异步事件循环,所有 I/O 操作都是非阻塞的,能够充分利用单线程处理大量并发连接。

分段存储模式:使用 Segment 管理消息历史,支持消息回溯和离线推送,同时通过定期清理控制存储开销。

7.2 扩展性设计

系统提供了良好的扩展性支持:通过 Protocol trait 可以添加新的协议版本支持;通过 AuthHandler 接口可以实现自定义认证逻辑;通过共享订阅策略可以扩展分发算法。这种设计使得系统能够适应不断变化的业务需求。

8. 技术规格

指标 规格
支持协议 MQTT v5 Extended(内部服务器), MQTT v5 Lite(外部 WebSocket)
传输层 TCP, WebSocket, TLS
最大并发连接 100,000+
最大消息Payload 20KB(可配置)
消息持久化 分段 CommitLog
QoS 级别 0, 1
共享订阅策略 RoundRobin, Random, Sticky
监控接口 Prometheus
相关推荐
遇见~未来6 小时前
第六篇_CSS进阶_深入浏览器与工程化
前端·css·rust
skilllite作者6 小时前
Warp 终端效能与交互体验全景展示
人工智能·后端·架构·rust
Rust研习社8 小时前
Rust 高性能内存缓存 moka 完全指南
开发语言·后端·缓存·rust
qcx238 小时前
Warp源码深度解析(七):Token预算策略——双轨计费、上下文溢出与摘要压缩
人工智能·设计模式·rust·wrap
DevilSeagull8 小时前
Rust 枚举(enum)深度解析:从定义到 Option 的安全之道
开发语言·后端·安全·rust·github
古城小栈19 小时前
从 cargo-whero 库中,找到提升 rust 的契机
开发语言·后端·rust
techdashen1 天前
Cloudflare 为何抛弃 NGINX,用 Rust 自研了一个代理
运维·nginx·rust