海狸IM 2.0 服务端技术解析:go-zero 微服务与消息链路

接上一篇《海狸IM 2.0 开放能力说明:OAuth2 接入与群推送机器人》,44 号文讲了六端发版,45 号文讲了 OAuth 与群推送。本篇从 后端工程 切入,把 2.0 服务端拆成几个关键技术点逐一分析:微服务怎么划、消息怎么发、实时怎么推、离线怎么补、开放怎么接。

IM 后端看起来就是「存消息 + 推消息」,真做起来会发现:会话、seq、多端同步、媒体态、MQ 解耦、运营 RPC……每个点都能单独写一篇文章。这篇先讲 总链路,后面系列会拆更细(OAuth 源码 walkthrough、datasync 协议、WS 连接管理等)。


1. 为什么 IM 后端一定要「拆」

很多初学者写 IM,习惯一个 Go 进程里塞:登录、好友、群、聊天、文件、推送。1.x 阶段能跑,用户一多、功能一加就痛苦:

  • 改 OAuth 误伤聊天接口
  • WS 连接数和 HTTP QPS 抢资源
  • 运营后台和用户 API 混路由,安全风险大
  • 单点发布,动一行代码全服务重启

海狸 IM 2.0 用 go-zero 按业务域拆成多个 API / RPC 服务,统一 gateway 对外 REST,ws 单独扛长连接。这不是为了「微服务而微服务」,而是 IM 天然有多条链路:

链路特点适合独立
聊天读写高 QPS、强一致 seqchat + datasync
实时推送长连接、内存态ws
第三方接入OAuth、Webhook、Robotopen + auth
运营管理低 QPS、高权限backend
媒体大 payload、对象存储file

二次开发建议:fork 后先认清你要动哪条链路,再进对应 app/ 目录,别在整个 monolith 里 grep。


2. go-zero 工程结构:API 与 RPC 两层

beaver-server 里每个业务域通常是 双份:

app/chat/
├── chat_api/     # 对外 REST,给客户端 / 网关调用
├── chat_rpc/     # 内部 gRPC,给其它服务调用
└── chat_models/  # 数据模型

为什么要分 API 和 RPC?

  • chat_api:面向 HTTP,做参数校验、JWT 鉴权、聚合多个 RPC 调用
  • chat_rpc:面向内部,SendMsg、GetSyncMessageMedias 等核心逻辑在这里
  • 其它服务需要发消息、查会话,直接调 chat_rpc,不必绕 HTTP

go-zero 的 .api 文件定义 REST 路由,.proto 定义 RPC,代码生成 handler / logic 骨架。读海狸源码时,习惯路径是:

handler → logic → rpc client → 对方 rpc logic → model

熟悉这条链,看任何模块都是同一套。


3. 核心模块深度对照

下面按 「你会改到什么」 排序,不是简单目录罗列。

3.1 gateway — 统一 front door

职责说明
路由转发把 /v1/chat/... 转到 chat_api
鉴权JWT 校验,未登录拒掉
跨域选择题;生产环境配合 Nginx

客户端(desktop / flutter)、beaver-manager、beaver-open 都只认网关地址,不直连 chat_api 内网端口。私有化定制时,改域名、改证书,动 gateway 配置和 Nginx 即可。

3.2 chat — IM 的心脏

app/chat 负责:

  • 会话(conversation)与用户会话关系(user_conversation)
  • 消息收发、seq 递增、撤回、编辑、已读 seq
  • 消息媒体态(语音未听等)
  • 通知类消息(入群、加好友等)

2.0 消息类型在 chat_rpc.proto 里定义得很全,例如:

message TextMsg {
  string content = 1;
}
message ImageMsg {
  string file_url = 1;
  int32 width = 2;
  int32 height = 3;
}
message VoiceMsg {
  string file_url = 1;
  int32 duration = 2;
}

设计要点:多媒体统一 file_url,和 2.0 客户端「一个字段走天下」对齐。扩展新消息类型时,先改 proto → 生成代码 → 双端加渲染组件,顺序别反。

3.3 datasync — 离线端的「变更摘要」

纯 WS 推解决不了 离线用户:用户睡觉八小时,上线后不能靠 WS 补历史。

datasync 模块提供 「自某时间点以来,哪些会话的 seq 变了」 这类摘要接口。客户端拿摘要和本地 seq 对比,再调 chat 的 sync 接口 按区间拉消息——这是典型的 「摘要 + 按需拉取」,微信、Telegram 类 IM 都是这个思路,只是字段名不同。

3.4 ws — 长连接与 MQ 消费者

app/ws 不直接写 MySQL,它干两件事:

  1. 维护用户 WebSocket 连接(内存 / Redis 映射)
  2. 消费 RocketMQ 消息,推给在线用户

chat 服务发完消息 不直接调 ws HTTP,而是往 MQ 丢 payload,ws 实例消费后 SendMsgToUser。好处:

  • chat 和 ws 进程解耦,chat 挂了不影响已有连接(极端情况)
  • ws 可多实例水平扩展
  • 广播模式:每个 ws 节点都消费,推本机在线连接(代码注释里写明了 广播模式:每个 WS 实例都消费)

MQ 消费者核心逻辑(简化理解):

// chat_api / chat_rpc 发 MQ
payload := map[string]interface{}{
    "command":  wsCommandConst.CHAT_MESSAGE,
    "type":     messageType,
    "targetId": recipientId,
    "body":     tableUpdates,
    "conversationId": conversationId,
}
RocketMQ.SendMessage(ctx, MqTopicWs, payload)

// ws_api 消费
ws_conn.SendMsgToUser(targetID, command, content)

这条 MQ 链路是读海狸后端最值得花时间的部分之一——搞懂它,就搞懂了「REST 写库 + 异步推 WS」。

3.5 friend / group / user

模块2.0 相关能力
friend好友关系、申请、黑名单
group群成员、权限、群助手机器人创建
user资料、设备、用户设置 JSON(PC 快捷键同步)

发消息前 chat 会 RPC 调 friend / group 校验权限(是否好友、是否在群、是否被禁言等),失败直接拒绝,不写库。

3.6 file — 媒体上传

图片、语音、视频、文件先走 file 服务上传 MinIO(或配置的对象存储),聊天消息里只存 fileUrl。2.0 刻意统一字段,减少 PC 和 Flutter 各写一套 URL 拼接逻辑。

3.7 open + auth — 开放能力

模块职责
open_rpc / open_portalOAuth2 应用、Token、Robot 相关 RPC
auth登录、JWT、扫码态

再次强调:群 推送机器人 走 bot_public + PC 群助手,不是 open 门户配置。45 号文讲产品边界,这里讲代码边界。

3.8 backend — 运营专用

beaver-manager 调 backend_admin 和各 RPC:用户 360、批量封禁、内容审核、在线监控。和普通用户 JWT 权限域隔离。

3.9 call — LiveKit 音视频

2.0 双端音视频基于 LiveKit。call 模块负责房间、Token 等,和 chat 消息链路分开,避免通话信令和聊天 MQ 搅在一起。


4. 一条消息的完整生命周期(2.0 实测链路)

以 用户 A 在 PC 给群友发一条文本 为例,把上面模块串起来。

4.1 客户端发起

beaver-desktop
  → POST /v1/chat/send(经 gateway)
  → chat_api handler
  → chat_rpc.SendMsg

客户端可先做 乐观 UI(本地插一条 sending),等服务端返回 seq 再改 sent——Flutter / PC 都是这个套路。

4.2 服务端处理(SendMsg 核心步骤)

sendmsglogic.go 里逻辑很重,概括成 7 步:

  1. 鉴权:发送者是否登录、是否有权在该会话发言
  2. 构建消息体:Proto → 内部 ctype.Msg(支持回复引用、多种 msgType)
  3. 分配 seq:会话内单调递增,是同步锚点
  4. 写 MySQL:消息表、会话 lastMsg、user_conversation version 等
  5. 更新 datasync 摘要:供离线端下次增量
  6. 组装 tableUpdates:把 messages / conversations / user_conversations 变更打成 WS 包体
  7. 按接收者循环发 MQ:每个群成员一条 MQ(携带 该成员自己的 user_conversation 更新)

第 7 步很关键:群聊 100 人,不是广播一个巨型包,而是每人一条 payload,只带自己的未读/version。这样客户端收到就能直接 upsert 本地表,不必再算「这条消息算不算我的未读」。

4.2 链路示意图

┌──────────┐   REST    ┌──────────┐  gRPC   ┌──────────┐
│ desktop  │ ────────► │ chat_api │ ──────► │ chat_rpc │
└──────────┘           └────┬─────┘         └────┬─────┘
                            │                    │
                            │                    ├── MySQL 写库
                            │                    ├── 更新 datasync
                            │                    └── RocketMQ (Topic: ws)
                            │                              │
                            │                              ▼
                            │                     ┌──────────────┐
                            │                     │ ws_api 消费   │
                            │                     │ SendMsgToUser │
                            │                     └──────┬───────┘
                            │                            │ WebSocket
                            ▼                            ▼
                     返回 seq 给 A              在线成员 B、C、D 收到推送

4.3 离线成员怎么办

用户 B 离线时,ws 推不到。B 下次打开 App:

  1. 调 datasync 接口:since=上次时间戳 → 拿到各会话 serverMaxSeq
  2. 和本地 chat_sync_status.seq 比,筛出落后会话
  3. 调 chatSyncApi(fromSeq, toSeq) 批量拉消息
  4. 落本地 SQLite / Drift,刷新 UI

在线走 WS,离线走 sync,两条路汇到同一张本地消息表——47 号文从客户端角度讲这条线。


5. 三个 IM 后端「必答题」在海狸里怎么答

5.1 消息顺序与 seq

问题:群聊里谁先谁后?编辑、撤回怎么算?

海狸做法:会话内 seq 严格递增,一条消息一个 seq。撤回/编辑是 同 messageId 更新 或新事件类型,客户端按 seq 排序渲染。不要用时间戳 alone 排序(时钟 skew 会乱)。

对比:

方案优点缺点
seq顺序可靠、增量 sync 简单需要服务端分配
时间戳实现直觉多端时钟不一致会乱序
向量时钟分布式强IM 产品很少对用户暴露

5.2 写走 REST,推走 WS

问题:为什么发消息不用 WebSocket 上行?

海狸做法:

  • REST:鉴权成熟、易日志、易限流、易重试
  • WS:下行广播、低延迟

上行也可以走 WS(有些 IM 这样),海狸 2.0 选 REST 写 + MQ + WS 推,对 go-zero 和网关生态更友好。二次开发别在 Vue 组件里直接 ws.send 发消息,除非你要改整条链路。

5.3 多 WS 节点怎么推

问题:部署两个 ws 实例,用户连在 B 节点,chat 怎么知道?

海狸做法:RocketMQ 广播消费,每个 ws 节点都收到 MQ,各自查本机有没有 targetId 的连接,有就推。连接表可在 Redis 维护 userId → nodeId(具体以实现为准)。

这是 IM 后端面试常考题,海狸源码里可以直接对着 mqconsumerlogic.go 讲。


6. 2.0 服务端「底座级」能力拆解

6.1 消息媒体同步

语音「未听 / 已听」不是改消息正文,而是 消息媒体态。2.0 单独做了 mark / sync 接口,datasync 也能拉媒体变更。2.0.1 修过跨库查询导致 PC 听了手机还显示未听——说明 消息体 和 媒体态 分表/分模块是对的。

6.2 用户设置跨端

PC 快捷键等配置走 user 模块 Settings API,存服务端 JSON。换电脑登录拉设置即可——Electron 本地 localStorage 做不了这个。

6.3 OAuth2 链路

开发者门户 beaver-open 建应用 → 用户 beaver-oauth 授权 → auth/open 签发 Token。和 chat 零耦合。接 OA 登录只动 open/auth,别动 sendmsg。

6.4 群推送 bot_public

外部 HTTP POST → 校验 Token → 写群消息 → 同样走 MQ → WS。和 OAuth 配置入口不同、代码路径不同。

6.5 运营 RPC

backend 调 user/chat 做 360 视图、审计、封禁。权限模型和用户 API 分开,避免「一个 JWT 既能聊天又能删库」。


7. 消息类型扩展:二次开发手册

要在海狸里加一种新消息(例如「投票卡片」),建议顺序:

  1. chat_rpc.proto 增加 PollMsg 定义
  2. go generate / go-zero 生成代码
  3. sendmsglogic.go 的 BuildMsgFromProto 增加 case
  4. file 服务如需封面仍走 fileUrl
  5. chat_api 暴露发送接口(若需新 REST 形状)
  6. beaver-desktop + beaver-flutter 各加渲染 widget / 组件
  7. datasync / WS 的 tableUpdates 结构若不变,通常不用改 ws 层

漏做第 6 步,就会出现「服务端能存,客户端显示空白」——IM 功能永远是 服务端 + 全客户端一起发。

当前 proto 已支持的主要类型包括:Text、Image、Video、File、Voice、AudioFile、Emoji、Notification、Link、CloudDoc 等,2.0 双端已对接常用 subset。


8. WebSocket 事件:不止 chat_message

事件方向示例用途
下行chat_message新消息、会话列表变更
下行read_seq 更新已读同步
下行typing正在输入(服务端有,UI 完善中)
下行bot_streaming_*机器人 / AI 流式同一条消息更新
下行好友/群/通知非聊天模块变更

流式回复(2.1 智能机器人会产品化)的核心:一个 messageId,多次 chunk,done 时落库全文。不是每个 token insert 一行 MySQL——那是踩坑写法。


9. 存储层:MySQL + Redis + Mongo + MinIO 各干什么

存储典型用途
MySQL用户、好友、群、会话关系、消息索引
Redis缓存、在线态、分布式锁(authlock 等)
MongoDB部分消息或大文档(按模块)
MinIO图片、语音、视频、文件 blob

别问「能不能只用 MySQL」——小 demo 可以,生产 IM 几乎都会 热数据 Redis + 冷数据 MySQL + 对象存储。海狸是按生产级分的,学习成本略高,但扩展路径清晰。


10. 和同类开源 IM 比后端(客观)

维度海狸 2.0常见感受
框架go-zero国内文档多,代码生成省事
拆分chat/ws/open/backend 清晰比单体重构友好
客户端Electron + Flutter 官方双端比「只给 SDK」的引擎型产品上手快
开放OAuth2 + 群 Webhook 已有交互式 @Robot 在 2.1
运营独立 manager + backend私有化团队常用

不是「最强」,而是 「能读源码、能改模块、能私有化」 的路线,适合中小团队自建。


11. 源码阅读路线(建议打印贴显示器旁)

第一轮:消息主线(4 小时)

  1. app/chat/chat_api/chat_api.api — 看有哪些 REST
  2. app/chat/chat_rpc/internal/logic/sendmsglogic.go — 发送全逻辑
  3. app/ws/ws_api/internal/logic/mqconsumerlogic.go — MQ → WS
  4. app/datasync/datasync_api — 摘要接口

第二轮:权限与社交(2 小时)

  1. app/friend / app/group RPC
  2. app/chat/.../recallmessagelogic.go 等

第三轮:开放(2 小时)

  1. app/open/open_rpc/open_rpc.proto
  2. app/open/open_portal OAuth 登录逻辑

配合 beaver-docs 在线文档,比盲读快一倍。


12. 常见问题(后端向)

Q:发消息延迟高,查哪里?

先看 chat_rpc 写库耗时,再看 MQ 堆积,最后看 ws 推送。别一上来优化 MySQL 索引,先打链路日志。

Q:群消息部分人收不到?

查该用户是否还在群、是否被禁言、WS 是否在线、离线是否 sync 了 seq。在线问题查 MQ 和 ws 连接;离线问题查 datasync + chatSync。

Q:能不能去掉 RocketMQ?

架构上 chat 直接调 ws 可以,但多实例 ws 会很难做。生产不建议。

Q:OAuth 和聊天要同一个服务吗?

不要。海狸已经拆开,保持边界。


13. 后续系列预告:一篇一个技术点

IM 复杂,完全可以像您说的 「拆技术点 + 分析」 连续写很多篇,例如:

计划篇目内容
消息 seq 与 datasync 协议详解摘要结构、冲突处理
WebSocket 连接管理与心跳ws 模块源码 walkthrough
OAuth2 在海狸里的 Code 流转配合 45 号文深挖
群推送 Webhook 与 bot_publiccurl 示例 + 验签
LiveKit 音视频接入call 模块
消息媒体态与跨端同步markMessageMedia 链路
beaver-manager 运营 RPC 设计backend 权限

本篇是 总览 + 消息主线;下一篇 47 讲 双端客户端怎么消费这套协议。


14. 小结

海狸 IM 2.0 服务端的价值,对开发者来说在于:

  1. go-zero 多模块,聊天 / 同步 / 推送 / 开放 / 运营边界清楚
  2. SendMsg → MQ → WS 链路完整,适合学习生产级 IM
  3. seq + datasync 支撑双端离线增量
  4. fileUrl 统一媒体,proto 扩展路径清晰
  5. 源码全开源,能 fork、能改、能写系列文章

如果你正在选型开源 IM、或准备面试 IM 后端,建议 clone beaver-server,对着 sendmsglogic.go 和 mqconsumerlogic.go 走一遍调试——比看十篇概论有用。


相关链接

项目源码:

  • 后端源码:https://github.com/wsrh8888/beaver-server
  • 移动端Flutter源码:https://github.com/wsrh8888/beaver-flutter
  • PC端源码:https://github.com/wsrh8888/beaver-desktop.git
  • 后台管理系统源码:https://github.com/wsrh8888/beaver-manager
  • 开放平台门户源码:https://github.com/wsrh8888/beaver-open
  • OAuth 授权登录页源码:https://github.com/wsrh8888/beaver-oauth

学习资源:

  • 在线文档:https://wsrh8888.github.io/beaver-docs/

核心教学视频:

  • 本地搭建教程合集:https://space.bilibili.com/269553626/lists/6075764?type=season
  • 服务器部署教程合集:https://space.bilibili.com/269553626/lists/6075828?type=season

上一篇:[海狸IM 2.0 开放能力说明:OAuth2 接入与群推送机器人](45.海狸IM 2.0:开放平台、OAuth与Bot,让IM不止于聊天.md)

下一篇:[海狸IM 2.0 双端客户端技术实践:Electron 与 Flutter 如何对齐 IM 协议](47.海狸IM 2.0 双端客户端技术实践:Electron与Flutter如何对齐IM协议.md)

更多推荐