返回首页

Titanium:基于 Erlang 的即时通讯后端

本文剖析了自定义即时通讯 Titanium 后端的演进:放弃 XMPP/FastAPI,转向 Erlang/OTP。描述了 pts 同步、分片、基准测试高达 5k RPS。适合高级开发者。

如何在 Erlang 上构建可扩展的即时通讯后端
Advertisement 728x90

# Titanium:自定义即时通讯的可扩展后端核心——从原型到生产

开发自定义即时通讯应用时,许多开发者会从 XMPP/ejabberd 或 Matrix/Synapse 入手。这些平台提供了现成的传输层、联邦机制和端到端加密。但要适配现代用户体验时,往往会遇到瓶颈。

基于 Erlang 的 XMPP/ejabberd 体积小巧、抗负载能力强,但存在以下问题:

  • XML 消息体带来解析开销。
  • MUC 群组无法线性扩展:参与者增多时 CPU 负载激增。
  • 部分 XEP 扩展实现会导致业务逻辑中的竞态条件。

Matrix 功能强大,但其分层架构让修改变得复杂:Python 实现的 Synapse 在实时任务中受 GIL 限制。

Google AdInline article slot

混合架构——如用 ejabberd 处理传输 + FastAPI 负责 API/OTP/反应——会导致状态分裂、XML↔JSON 转换和额外队列。单一真相源在组件间变得模糊。

要点: 从零构建后端,能实现对状态和性能的完全掌控,更为简洁。

Titanium v1:FastAPI 负载测试

首个 Titanium 版本基于 FastAPI + WebSocket,实现核心功能:聊天、UIN、媒体、pts 同步。原型在 1000 用户场景下承受 500 RPS,消息 120 条/秒,媒体 40 条/分钟,断线率 7%。

Google AdInline article slot

RPS 测试覆盖完整路径:发送 → 投递 → 确认 → 同步。具体包括:

  • 访问权限校验。
  • pts 更新(消息点,用于增量同步)。
  • 实时 WebSocket 推送。
  • 网络不稳时的重试。
  • 离线客户端的更新日志。

规模化时暴露问题:

  • GIL 限制 WebSocket 并发。
  • PostgreSQL 在 pts 表高争用时锁表。
  • 媒体存储需临时链接,而非永久。

Titanium 架构:真相源头

Titanium 定义了严格的状态管理规则:

Google AdInline article slot
  • 服务器即真相源: 绝不信任客户端,所有变更走 API。
  • Pts 机制: 类似 Telegram 的消息点增量同步。
  • 更新日志: 不可变日志,用于状态重放。
  • 分布式状态: 按 UIN 分片,无粘性会话。
  • 媒体: S3 风格临时签名,24 小时 TTL。

核心组件:

  • 网关: WebSocket + HTTP/2,限流,JWT。
  • 核心: 业务逻辑、pts、ACL(按 UIN/群组)。
  • 存储: PostgreSQL(状态)+ Redis(会话/队列)+ S3(媒体)。
  • 工作者: Celery/RQ 处理异步任务(媒体处理、机器人)。

Pts 同步示例(伪代码):

% 客户端请求增量更新
handle_get_updates(ReqPts) ->
  Updates = db:fetch_updates_after(ReqPts, UserId),
  NewPts = lists:max([U#update.pts || U <- Updates]),
  {reply, #{updates => Updates, pts => NewPts}}.

转向 Erlang/OTP:可扩展性与韧性

Python 无法处理 10k 并发 WebSocket,Erlang/OTP 完美解决:

  • Actor 模型:每个 UIN 对应一个监督树 + gen_server。
  • VM 热重载,无宕机。
  • 内置 Mnesia/ETS,低延迟存储。
  • Cowboy 支持单节点 100 万+ WebSocket 连接。

基准测试:

| 场景 | FastAPI (Python) | Titanium (Erlang) |

|----------|-------------------|--------------------|

| 1k 用户,120 消息/秒 | 500 RPS | 5k RPS |

| 10k WS | 20% CPU | 5% CPU |

| 媒体 100/分钟 | 2s 延迟 | 200ms |

Gen_server 会话示例:

-module(session).
-export([start_link/1, handle_message/2]).

handle_call({send_message, Msg}, _From, State) ->
  case validate_rights(Msg) of
    ok ->
      NewPts = State#state.pts + 1,
      save_update(Msg#update{pts=NewPts}),
      broadcast_to_peers(Msg),
      {reply, ok, State#state{pts=NewPts}};
    {error, Reason} -> {reply, {error, Reason}, State}
  end.

群聊与边缘场景

群聊是难点。摒弃 MUC,转向:

  • 分片房间: 领导者由 hash(UIN 列表) 决定,从节点复制增量。
  • 在线状态: ETS 存储在线/离线,重新连接时 pubsub。
  • 恶劣网络: 乐观发送 + 服务器确认,从日志补漏。

处理场景:

  • 重复发送(msg_id 幂等性)。
  • 数据包乱序(时间戳 + 序列号)。
  • 休眠/唤醒标签页(长轮询 fallback)。
  • 机器人 webhook 重试(死信队列)。

核心要点

  • Titanium 不是 MTProto 克隆,而是一个精简实时栈:pts、日志、分片。
  • Erlang/OTP 提供 10 倍 RPS,CPU 仅 Python 的 1/4。
  • 服务器真相源避免客户端不一致。
  • 媒体/机器人异步卸载,核心专注投递。
  • 扩展:3 节点处理 50k 用户、1k 消息/秒,无需联邦。

— Editorial Team

Advertisement 728x90

继续阅读