架构 #

Matrix 与常规的 client-server 聊天有几个根本区别:

  • 事件溯源(Event-sourced)。 每一条消息、每一次表情反应、每一次编辑、成员变动、头像更新、话题修改都是一个 事件(Event)。房间是一份按序增长的事件日志,而不是一张可随时覆写的"当前状态"表。这就是为什么可以精确重放历史、回到任意时间点、干净地桥接到外部网络——因为一切都是事件。
  • 最终一致。 当联邦中的服务器对事件顺序存在分歧(网络分区、慢链路)时,Matrix 用一套确定性的 状态解析(State Resolution) 算法来同意一份共同的当前状态,且不会阻塞写入。它牺牲了强一致以换取可用性——即便半个网络不可达,对话仍然能流动。
  • DAG 而非线性日志。 事件不是单一链条,而是一个 Merkle DAG:每个事件都引用它的前驱。这天然处理并发写——不同主机服务器上两个人同时发的消息都指向同一份前置状态,然后都会被编织进历史。
  • 可扩展。 事件类型是开放的。官方定义的类型(m.room.messagem.room.memberm.reaction...)覆盖日常场景;自定义类型可以拿来做投票、地图、CRDT、协同编辑器、游戏状态、IoT 遥测——任何需要"带鉴权的去中心化复制日志"的东西都行。
  • 加密与传输解耦。 E2EE 是协议的一部分,不是事后补丁。对加密房间而言,每一台参与的服务器只能看到密文。详见 端到端加密
  • HTTP 原生。 所有 API 都是 HTTPS 上的 JSON。没有自定义二进制格式、没有私有 TLS 扩展——用 curl 就能调试。

整体架构 #

                    联邦(Server-Server API,HTTPS+JSON)
                    ┌─────────────────────────────────────┐
                    │                                     │
    ┌───────────┐   │    ┌───────────┐       ┌──────────┐ │    ┌───────────┐
    │ Element   │──┼──▶ │ your.     │ ◀───▶ │ matrix.  │◀┼─── │ FluffyChat│
    │ (Web)     │   │    │ meldry.   │       │ org      │ │    │ (手机)    │
    │           │◀─┼─── │ com       │       │          │ │─── │           │
    └───────────┘   │    └───────────┘       └──────────┘ │    └───────────┘
                    │          ▲                    ▲     │
                    │          │                    │     │
                    │          ▼                    ▼     │
                    │    ┌───────────┐       ┌──────────┐ │
                    │    │ 桥接、     │       │ AppSvc、 │ │
                    │    │ AppSvc    │       │ 机器人   │ │
                    │    └───────────┘       └──────────┘ │
                    └─────────────────────────────────────┘
   Client-Server API                     Server-Server API
   (Element 讲的协议)                   (主机服务器之间讲的协议)
  • 客户端Client-Server API:HTTPS + JSON,协议规范定义。Element、FluffyChat、Nheko、Cinny、你自己的机器人——它们打的都是你主机服务器上的同一组 endpoint。
  • 主机服务器 之间讲 Server-Server API(联邦),基于 HTTPS + 带签名的 JSON。这就是 your-name.meldry.com 上的一个用户为什么可以不经任何中间人直接跟 matrix.org 上的用户聊天。
  • 桥接和 Appservice 挂在主机服务器上,通过虚拟用户和虚拟房间扩展它(见 桥接Appservice)。

你挑客户端,Meldry 运行主机服务器,剩下的交给协议。

通讯原理 #

Client-Server API #

当 Element 或任何客户端跟你的主机服务器通讯时,它做的事情不外乎四类:

  1. /sync——一个长连接轮询,返回自上次请求以来发生的一切:房间里的新事件、新邀请、在线状态变化、设备间消息(to-device)。客户端会永远保持一个 /sync 开着;服务器最多挂起 30 秒,然后把积累到的内容返回。
  2. PUT /rooms/{id}/send/{event_type}/{txn_id}——发送一个新事件(消息、反应、撤回)。txn_id 保证发送是幂等的,重试不会重复。
  3. 状态查询——拉取当前房间状态、成员列表、权限等级。
  4. 媒体上传 / 下载——二进制通过独立的 content-repository endpoint 传输,通过 mxc:// URI 引用。

全部用登录时取得的 access token 做 bearer 认证。完整的 API 见 Matrix client-server 协议规范

Server-Server API(联邦) #

当你的主机服务器需要把一条消息发进一个在其他服务器上也有成员的房间时,它会:

  1. 用服务器的 Ed25519 密钥对事件签名。
  2. 通过 HTTPS 把事件发到对端服务器的联邦 endpoint。
  3. 对端服务器依据发送方服务器的公钥校验签名(公钥也通过 HTTPS 取,有自己的一套 key verification)。
  4. 对端服务器把事件应用到它本地那份房间状态。

对端服务器不会盲目信任发送方——它会校验每一个事件的签名,并确认事件的"auth chain"(解释为什么这个事件被允许的那一串 auth 事件)符合房间的权限等级规则。

当两台服务器并发添加事件时,状态解析 算法会按 auth_events 和一个确定的平局规则选出一份权威顺序,两边最终落到相同的状态。

事件图(DAG) #

房间里每个事件都有一个 prev_events 字段,列出它对应响应的那些事件的哈希。在多参与者跨多服务器的情况下,prev_events 可以指向多个前驱,形成一张有向无环图。客户端沿 DAG 反向回溯以展示历史;服务器用 DAG 来检测并在联邦恢复后修复分叉。

房间与空间 #

房间 #

房间 是一份带鉴权规则、共享复制的事件日志。每个房间都有:

  • 一个永久的内部 ID !abc123:server
  • 零或多个人类友好别名 #general:server
  • 一张 权限等级 图——每个用户一个整数加默认值,决定谁可以发消息、邀请、踢人、封禁、撤回、改话题、升级房间等等。PL 0 是普通成员,PL 50 是管理员,PL 100 是所有者(这些是惯例,不是写死的)。
  • 可选的端到端加密
  • 一个 历史可见性 设置——新加入者能否看到加入之前的历史。

房间可以公开(在主机服务器目录里列出)或私有(邀请制)。房间不绑定到某一台主机服务器——起源于 matrix.org 的一个房间可以同时有成员在 your-name.meldry.com、在 kde.org、在 element.io,每一台主机服务器都复制一份自己的事件副本。

空间 #

空间(Space) 是一种特殊的房间,它的"成员"是其他房间和其他空间。空间是 Matrix 给"我想要一个 Slack workspace"或"我想要一个 Discord 服务器"的答案——把相关房间组织成可浏览的层级。可以嵌套空间,标记某些房间为"推荐",也可以把空间的一部分选择性分享给外人。

底层实现上,空间就是一个 room type 为 m.space 的房间,通过 m.space.child 状态事件指向它的子节点。

下一步 #

  • 身份与账户——账户、前缀符号与设备 ID 是怎么工作的
  • 端到端加密——建立在本架构之上的 E2EE 层
  • 生态——今天有哪些实现在讲这套协议