Files
im_server/docs/IM-多开跨Pod消息投递改造方案.md
T

25 KiB
Raw Blame History

IM 多开与跨 Pod 消息投递改造方案

日期:2026-09-12
适用范围:IMServer、Admin Server / HotGo、Redis、Kafka、JPush
关联文档:docs/对接文档最新/IM-前后端对接文档.md、docs/IM-服务端架构与新功能开发指南.md、docs/IM-Redis三层数据分层改造方案.md

1. 背景与目标

多开指同一用户在多设备、多 App 实例、多浏览器窗口或同设备多连接同时在线。服务端必须保证任意一个 Pod 收到的实时消息、业务通知、在线状态变化和账号治理命令,都能正确投递到同一租户内该用户在所有相关 Pod 上的 WebSocket / Long Polling 连接。

当前代码已有以下基础:

  • SessionStore 管理本 Pod 内的实时连接。
  • Redis presence 记录用户在线状态、session 集合和节点维度在线用户。
  • 群消息已有 redis_fanout.go,通过 Redis Pub/Sub 通知其他 Pod。
  • 群消息已拆到 im_group_messages,并具备 Redis seq、client_mid 幂等和 Kafka 异步持久化基础。
  • im_business_events.go 仍使用租户库 business event outbox 做本地轮询投递。

本功能仍处于开发完善阶段,尚未上线。本次改动按“完整补齐多开跨 Pod 能力”处理,不按线上热修补或灰度兼容旧缺陷的思路处理。

本次改造目标:

  1. 不再依赖租户库 business event outbox 作为 C 端实时通知主链路。
  2. 建立统一的跨 Pod 实时投递总线,覆盖个人消息、群消息、系统消息、好友申请、好友状态、群治理、账号治理等需要多开同步的数据。
  3. IMServer 只把本 Pod 连接保存在内存,全局在线、节点路由和跨 Pod 投递统一依赖 Redis。
  4. Admin Server 不直连 IM 租户库,不直接操作用户 WS;后台写操作通过 Kafka Command 到 IMServer,由 IMServer 更新租户库并通过实时投递总线通知在线端。
  5. 离线 Push 只做提醒和唤醒,不作为消息正文或业务状态真源。

1.1 前置确认

实施前必须确认生产部署是否启用 Tinode 原生 cluster_config:

  • 如果未启用,Redis realtime dispatcher 是跨 Pod 实时投递唯一主链路。
  • 如果启用,Topic 消息不要同时走 Tinode 原生 Topic master/proxy 和 Redis 广播式 dispatcher,否则可能重复投递。当前代码实现策略是:globals.cluster != nil 时,Topic 消息 fanout 不再额外发布 Redis realtime;session control 仍可通过 Redis 做幂等控制补充。

未启用原生 cluster 时,每个 Pod 必须配置唯一节点 ID。优先级为:

  1. node_id 配置项。
  2. IM_NODE_ID 环境变量。
  3. HOSTNAME 环境变量。
  4. 本地开发兜底值 local。

多 Pod 环境不能让所有实例都使用 local,否则 Redis fanout 会误认为其他 Pod 是本节点并跳过投递。

2. 核心结论

跨 Pod 实时投递使用 Redis Pub/Sub 或 Redis Stream,不使用数据库 outbox,也不使用 Kafka 作为 WebSocket fanout 主链路。

Kafka 仍保留以下职责:

  • Admin Server 到 IMServer 的后台写命令。
  • IMServer 到 Admin Server 的命令结果、审核结果、摘要同步。
  • 私聊/群聊消息异步持久化。
  • 文件安全检测、离线 Push 任务、审计、死信。

Redis 实时投递总线负责:

  • 这个 Pod 收到消息后,通知其他 Pod。
  • 其他 Pod 根据本地 SessionStore 找到真实 WS 连接并投递。
  • 通过事件 ID / 消息 ID / topic+seq 做短期去重,优先复用已有 ClaimShortState / ClaimShortStateValue Redis 原语。

MySQL 负责:

  • 最终状态和历史补偿。
  • 用户、好友、群、系统通知、消息、文件、设备会话等可追溯数据。
  • 客户端重连后按 topic+seq、通知 ID 或业务 ID 拉取缺口。

3. 总体架构

Client A
  -> Pod-1 WebSocket
  -> Session / Topic / HTTP handler
  -> 权限、幂等、seq、状态写入
  -> 投递本 Pod 本地 sessions
  -> Redis realtime bus
       channel/stream: im:{tenant_id}:rt:{kind}
       payload: envelope + server message
  -> Pod-2 / Pod-3 subscriber
  -> 按 tenant + target user/topic 找本 Pod sessions
  -> queueOut 到对应 WS / Long Polling

Client B reconnect
  -> hi -> token login -> sub me/topics
  -> 按 seq/read/recv 或 notification cursor 从 MySQL/Redis 热缓存补齐

Admin Server 写操作:

Admin Server
  -> 校验后台权限
  -> 写 hotgo 命令状态
  -> Kafka im.admin.command
  -> IMServer 任一 Pod 消费
  -> 更新租户 MySQL / Redis / Topic 权限快照
  -> Redis realtime bus 通知在线 C 端
  -> Kafka im.admin.command.result 回写 Admin

4. 跨 Pod 实时投递总线设计

4.1 统一 Envelope

所有需要投递到其他 Pod 的实时事件统一包装:

{
  "event_id": "rt_01K...",
  "tenant_id": "1",
  "tenant_code": "default",
  "kind": "message|topic_event|user_event|presence|session_control",
  "target_type": "user|topic|device|session",
  "target": "usr_xxx 或 grp_xxx 或 p2p_xxx",
  "from": "usr_sender",
  "source_node": "im-pod-1",
  "skip_sid": "sid_sender",
  "dedup_key": "message:grp_xxx:123",
  "occurred_at": "2026-09-08T10:00:00.000Z",
  "payload": {}
}

字段约定:

  • event_id:全局唯一,消费者短期去重。
  • tenant_id:必填,Redis key 和投递过滤必须带租户。
  • kind:决定消费者如何解释 payload。
  • target_type=user:投递给某用户在本 Pod 的所有连接。
  • target_type=topic:投递给本 Pod 已订阅该 Topic 的连接。
  • target_type=device:投递或控制某用户指定设备连接。
  • target_type=session:精确控制某个 Session,主要用于本 Pod 本地,跨 Pod 可选。
  • skip_sid:避免给发起连接重复投递,除非业务要求多端含当前端也收到。
  • dedup_key:短期幂等键,例如 msg:topic:seq、friend_request:id:version。
  • payload:可以是 Tinode ServerComMessage 的 JSON,也可以是业务刷新事件。

4.2 Redis Channel 建议

首版可以使用 Pub/Sub,要求客户端重连补偿兜底:

im:{tenant_id}:rt:user
im:{tenant_id}:rt:topic
im:{tenant_id}:rt:presence
im:{tenant_id}:rt:session_control

如果需要更强的可观测和短暂重放,可升级为 Redis Stream:

im:{tenant_id}:stream:rt
consumer_group = imserver:{node_id}

选择建议:

  • 在线消息 fanout:Pub/Sub 足够,丢失后客户端按 seq 补偿。
  • 踢设备、封禁、强制退出:建议 Pub/Sub + Redis control key 双写,避免订阅瞬断导致控制命令遗漏。
  • Admin 重要业务变更:状态已落 MySQL,实时事件可以 Pub/Sub;客户端重进页面重新 GET。

4.3 本 Pod 投递规则

收到跨 Pod envelope 后:

  1. 丢弃 source_node == currentNodeID() 的回环事件,除非显式需要自消费。
  2. 使用 Redis rt:dedup:{event_id} 或本地 LRU 做短期去重。
  3. 校验 tenant_id,不得跨租户投递。
  4. 根据 target_type 选择本地 sessions:
    • user:session.uid == target && session.tenantID == tenantID
    • topic:session.hasSub(target) && session.tenantID == tenantID
    • device:session.uid == uid && session.deviceID == device_id
  5. 对每个匹配 Session 调用 queueOut。
  6. 队列满时记录指标,不阻塞 Redis 订阅协程;客户端靠重连补偿。

5. 哪些数据需要多开处理

类别 场景 是否需要跨 Pod 实时投递 目标 最终补偿来源 说明
个人消息 P2P 文本、图片、语音、文件、名片、通话邀请消息 是 P2P Topic 在线成员、发送人其他端 私聊消息表 / Topic seq Pod-1 收到发送后,本 Pod 投递并通过 Redis 投递到接收人和发送人其他端所在 Pod。
群消息 群文本、媒体、文件、@、合并转发 是 群 Topic 在线成员 im_group_messages / Redis 热消息 已有群 fanout 雏形,需要纳入统一 envelope 和节点去重。
发送回执 pub ack、消息发送成功 seq 当前连接本地为主,发送人其他端可选 发送人其他端 消息表 / seq 当前连接必须收到 ctrl 202;其他端同步消息用 {data},不依赖 ctrl。
送达/已读 note recv/read 是 对端所有在线端、本人其他端 subscription 游标 多端 read/recv 取最大值,所有端刷新气泡状态和未读数。
正在输入 kp/kpa 临时状态 是,但 TTL 短 对端在线端或群内订阅端 无 不落库,允许丢失,限频。
消息撤回/编辑 recall、revision 是 Topic 在线成员 消息版本表 / 消息状态 HTTP 成功后发 topic 事件,在线端刷新 topic+seq+version。
删除/清空记录 当前用户视图删除 是,用户维度 本人所有端 删除游标 / del 记录 只影响操作者多端,不影响对方。
系统消息 系统通知列表新增、已读 是 目标用户所有端 im_system_notifications 新增需要提醒所有端;已读要同步清红点。
好友申请 创建、同意、拒绝 是 申请人、被申请人所有端 好友申请表、系统通知表 刷新新朋友列表、bootstrap badge、会话 context。
好友关系 添加、删除好友、拉黑/取消拉黑、备注 是 双方或操作者所有端 好友/黑名单表 输入区权限、会话标题、通讯录和黑名单需要同步。
好友在线状态 上线、离线、最近活跃 是 订阅相关 P2P/me Topic 的在线端 Redis presence 不应扫库;presence 以 Redis TTL 为准。
群成员 入群、退群、踢人、邀请审批 是 群 Topic 在线成员、相关用户所有端 群成员表、订阅表 同步群人数、成员列表、会话权限和系统提示。
群角色 群主转让、管理员增删 是 群 Topic 在线成员、目标用户所有端 群成员角色表 必须刷新 Topic perUser 权限快照和 Redis 成员缓存。
群禁言 全员禁言、成员禁言/解禁 是 群 Topic 在线成员、被禁言用户所有端 群治理/禁言表 输入框权限不能只靠前端本地状态。
群公告 发布、更新、已读 是 群 Topic 在线成员;已读为本人所有端 群公告表、公告已读表 顶部入口、公告详情和红点同步。
群资料 名称、头像、简介 是 群 Topic 在线成员 群表 / Topic desc 会话标题、列表摘要、成员页都要刷新。
群文件 上传登记、删除、审核结果 是 群 Topic 在线成员 群文件表、文件引用表 文件正文不走 Redis,只投递文件项变化事件。
会话设置 置顶、免打扰、群昵称、草稿 是,用户维度 本人所有端 subscription/private 设置表 不影响其他用户。
未读数/入口红点 bootstrap badge、会话 unread、系统通知 unread 是 本人所有端 seq/read、系统通知表、业务表 实时事件只触发刷新;服务端 GET 返回最终值。
设备会话 新设备登录、退出当前设备、踢设备、全端退出 是 被控制用户所有端或指定设备 设备会话表、Token auth_version、Redis control key 需要跨 Pod 关闭 WS。
账号治理 封禁、解封、冻结、注销、密码重置 是 目标用户所有端 用户状态、auth_version Admin command 触发,IM 执行后踢在线并清 Token。
安全风控 风险弹窗确认/取消 是,用户维度 操作者所有端 风控挑战表 / Redis action token 防止多端同时继续敏感操作。
会议/工作台 会议通知、日报月报提醒、工作台红点 是 目标用户所有端 对应业务表 会议笔记不产生通知,但页面返回后主动刷新。
文件安全 审核通过/拒绝、文件失效 是 文件引用可见用户 文件元数据/引用表 通知在线端刷新消息卡片或文件列表。
离线 Push 用户离线、后台或不可实时投递 不属于 WS 多开,但需去重 JPush alias / registration_id Push 任务表或 Kafka 多设备允许同 alias,payload 只带定位字段。

6. IMServer 改造方案

6.1 新增 Realtime Dispatcher

建议新增模块:

server/realtime_dispatcher.go
server/store/realtime_runtime.go

职责:

  • 发布跨 Pod envelope 到 Redis。
  • 订阅 Redis envelope 并投递到本 Pod SessionStore。
  • 提供 PublishToUser、PublishToTopic、PublishSessionControl 等方法。
  • 统一 event_id、dedup_key、skip_sid、租户校验和指标。

接口示意:

type RealtimeTarget struct {
    TenantID types.TenantID
    Type     string // user/topic/device/session
    Target   string
    SkipSid  string
}

func PublishRealtime(target RealtimeTarget, msg *ServerComMessage) error
func PublishRealtimeEvent(target RealtimeTarget, event IMRealtimeEvent) error

现有 redis_fanout.go 后续可以改为调用统一 dispatcher,保留兼容封装。

6.2 个人消息跨 Pod

当前群消息已有专用 fanout,个人消息也需要同等级处理。

发送流程:

  1. WS 收到 P2P pub。
  2. Session 完成租户、登录、Topic 归一化。
  3. Topic 校验好友关系、黑名单、风控、文件 ACL。
  4. 使用 client_mid 做幂等,生成 Topic seq。
  5. 本 Pod 投递:
    • 当前连接返回 ctrl 202。
    • 发送人其他端收到同一 {data}。
    • 接收人在本 Pod 的端收到 {data}。
  6. Redis realtime bus 投递到其他 Pod:
    • target_type=topic,target=p2p...
    • 或拆成 target_type=user 给双方用户。
  7. 异步持久化到 MySQL。
  8. 不在线设备只生成 Push 任务,客户端重连按 seq 补齐。

关键点:

  • noecho=false 时发送人多端应同步消息。
  • skip_sid 只能跳过当前连接的重复 {data},不能跳过发送人其他端。
  • P2P 传入 usr... 时必须先归一化为真实 P2P Topic。

6.3 群消息跨 Pod

保留当前 Redis seq、client_mid 幂等、热消息和 im_group_messages 最终落库模型。

需要补强:

  • redis_fanout.go 使用统一 envelope。
  • Redis channel 从全局 im:0:group_fanout 调整为带租户或 envelope 必带 tenant_id。
  • 投递时优先使用 topic:{topic}:node:{node_id}:online_members,避免所有 Pod 都扫描全部 session。
  • 消费端支持 topic 已加载和未加载两种路径:
    • Topic 已加载:投递到 hub.routeSrv,由 Topic 统一处理。
    • Topic 未加载:直接扫描本 Pod 已订阅该 Topic 的 session 投递。
  • Redis Pub/Sub 丢失不补发,客户端重连通过 since=last_seq+1 补齐。

6.4 业务状态事件跨 Pod

替换 im_business_events.go 的数据库 outbox 轮询模式。

HTTP handler 或 Store 写成功后:

  1. 同步写 MySQL 最终状态。
  2. 失效 Redis 缓存和 Topic 权限快照。
  3. 调用 realtime dispatcher 发布事件。
  4. 如需通知离线用户,投 Kafka Push 任务。

业务事件 payload 示例:

{
  "info": {
    "topic": "usr_target",
    "from": "usr_actor",
    "what": "im",
    "event": "friend.request.created",
    "payload": {"request_id": "frq_1"},
    "ts": "2026-09-08T10:00:00Z"
  }
}

群事件:

{
  "info": {
    "topic": "grp_xxx",
    "from": "usr_actor",
    "what": "group",
    "event": "group.role.updated",
    "payload": {"topic": "grp_xxx", "user_id": "usr_2", "role": "admin"},
    "ts": "2026-09-08T10:00:00Z"
  }
}

6.5 Session Control 跨 Pod

设备退出、踢设备、全端退出、密码重置、账号封禁必须跨 Pod 生效。

建议 Redis key:

im:{tenant_id}:session_control:user:{uid}:version
im:{tenant_id}:session_control:device:{uid}:{device_id}:version
im:{tenant_id}:session_control:event:{event_id}

流程:

  1. IMServer 或 Admin command 更新 MySQL 用户状态、设备会话状态或 auth_version。
  2. 写 Redis control version,TTL 可 1 到 7 天。
  3. 发布 target_type=user/device 的 session_control envelope。
  4. 各 Pod 收到后调用 SessionStore.EvictUser 或 EvictUserDevice。
  5. Session 心跳或上行业务包也检查 Redis control version,防止 Pub/Sub 丢失。

6.6 Presence 多开规则

Redis presence 是全局在线真源:

  • 用户登录成功:增加 session,写 presence:user:{uid}、presence:user:{uid}:sessions、presence:node:{node_id}:users。
  • 订阅群 Topic:写 topic:{topic}:online_members 和 topic:{topic}:node:{node_id}:online_members。
  • 心跳:刷新 session TTL、user TTL、已订阅群成员 TTL。
  • 断线:移除 session;最后一个 session 离线时标记 online=0。
  • 节点异常:依赖 TTL 自动过期。

好友在线状态变化:

  • 不扫描所有用户。
  • 只通知与该用户有订阅关系或正在会话的在线端。
  • 客户端展示最近在线状态时,HTTP/WS 查询均以 Redis presence 为准。

7. Admin Server 改造方案

7.1 Admin 不直接做 WS 投递

Admin Server / HotGo 只负责后台权限、配置、审核和命令生产,不直接连接 IMServer 内存态,也不直接写租户库。

Admin 写操作统一:

  1. 校验后台管理员权限。
  2. 写 hotgo 命令表,记录 command_id/event_id/idempotency_key/actor/request_id。
  3. 发送 Kafka im.admin.command。
  4. 等待 IMServer im.admin.command.result 更新命令状态。

7.2 Admin 命令触发的多开场景

Admin 操作 Kafka Command IMServer 动作 C 端实时投递
封禁用户 user.disable 更新用户状态、递增 auth_version、踢在线 session_control user.disabled 给目标用户所有端
解封用户 user.enable 更新用户状态、失效缓存 可选 user.enabled 给本人和相关管理视图
重置密码 user.password.reset 更新密码、递增 auth_version、撤销 Token session_control security.password_reset
踢设备 user.device.revoke 撤销设备会话 session_control device.revoked
解散群 group.delete 解散 Topic、撤销订阅和二维码 group.deleted 给群在线成员
踢群成员 group.member.remove 更新成员状态和 Topic 订阅 group.member.removed 给群成员和被踢用户多端
设置管理员 group.admin.set 更新角色、刷新权限快照 group.role.updated
删除管理员 group.admin.remove 降级角色、刷新权限快照 group.role.updated
禁言成员 group.member.mute 写禁言表、刷新权限 group.mute.updated
撤回/处置消息 message.moderate 更新消息状态、审核记录 message.recalled 或 message.moderated
文件审核处置 file.audit.update 更新文件状态和引用可见性 file.unavailable 给引用可见用户
举报处理结果 abuse.report.resolve 更新举报状态、生成系统通知 system_notification.created 给举报人

7.3 Admin 摘要同步

Admin 页面需要列表、统计和审核台数据,不应通过实时总线获得。

推荐:

  • Admin 查询租户详情:调用 IM 内部只读 API。
  • Admin 高频列表:维护 hotgo 摘要表。
  • IM 状态变更后通过 Kafka result/event 同步摘要给 Admin。

这些摘要同步不属于 C 端多开实时投递,不要求发到用户 WS。

8. Redis、Kafka、MySQL 边界

组件 用途 不应用于
Redis Pub/Sub / Stream 在线跨 Pod WS 投递、session control、presence 变化、短期去重 最终历史、长事务、后台命令可靠执行
Redis KV/Set/Hash presence、节点在线用户、Topic 在线成员、短期幂等、热消息 长期业务主数据
Kafka Admin command/result、消息持久化、Push、文件安全、审计、死信 WebSocket 实时 fanout、验证码、在线状态
MySQL 租户库 最终状态、历史查询、重连补偿、审计 在线连接表、实时 fanout
hotgo Admin 后台主数据、配置、命令状态、摘要 C 端 IM 消息和租户业务最终状态

9. 渐进实施计划

阶段一:统一跨 Pod 投递

  1. 新增 realtime dispatcher。
  2. 先增加 P2P 个人消息跨 Pod 投递。当前 P2P 是完全空白,应优先于群消息迁移。
  3. 将现有群消息 Redis fanout 改为 dispatcher,并修复 ServerComMessage 内部路由字段 json:"-" 导致跨 Pod 丢 TenantID/RcptTo/SkipSid 的问题。
  4. 增加用户维度事件投递,先覆盖好友申请、系统通知、已读同步。
  5. 增加 session control 跨 Pod 踢设备/全端退出。
  6. 同步上线 Redis 订阅健康检查、发布/消费/队列满指标和基础告警。

阶段二:替换 business event outbox

  1. 已停止把 C 端在线通知写入 im_business_events_outbox,本次改造直接替换为 realtime dispatcher。
  2. publishIMRelationEvent、publishIMMeetingEvent 等改为:
    • 需要历史展示的系统通知写 im_system_notifications。
    • 在线刷新直接调用 realtime dispatcher。
    • 离线提醒投 Kafka Push。
  3. 已移除 startIMExternalBusinessEventPoller。
  4. registration.invite_used 改为注册提交后直接同步邀请码使用聚合;security.password_reset 改为密码重置后直接跨 Pod 踢线。

阶段三:Admin 联动

  1. Admin Server 后台写操作全部通过 Kafka command。
  2. IMServer command consumer 执行业务状态更新。
  3. IMServer 执行成功后通过 realtime dispatcher 通知 C 端。
  4. IMServer 通过 Kafka result/event 回写 Admin 命令状态和摘要。

阶段四:可观测和压测

  1. 完善跨 Pod 投递指标:发布数、消费数、投递 session 数、丢弃数、队列满数、延迟。
  2. 完善 Redis 订阅健康检查和告警。
  3. 压测同一用户 10 端、多 Pod P2P 消息、千人群/万人群消息。
  4. 验证 Pub/Sub 丢失后重连补偿路径。

10. 验收用例

用例 步骤 预期
同用户多端收个人消息 用户 A 两端分别连 Pod-1、Pod-2,用户 B 发 P2P 消息到 Pod-1 A 两端都收到消息,B 其他端也同步,消息只落一条。
群消息跨 Pod 群成员分布在 Pod-1、Pod-2、Pod-3,Pod-1 收到群消息 所有在线订阅成员实时收到;离线成员重连按 seq 补齐。
重复发送幂等 断线后使用同一 client_mid 重发 返回同一 seq,不重复落库,不重复 Push。
已读多端同步 A 在手机读到 seq=100 A 其他端未读清除,B 多端看到 read 更新。
好友申请多端同步 B 向 A 发好友申请 A 所有在线端刷新新朋友红点和申请列表。
系统通知已读同步 A 在 Pod-1 上把系统通知标记已读 A 在 Pod-2 的端红点同步消失。
群角色变更 Admin 将 A 设为管理员 A 所有端群管理入口出现;群在线成员看到角色标识刷新。
禁言即时生效 Admin 禁言 A,A 另一个端在 Pod-2 继续发消息 服务端返回 GROUP_MUTED,不能成功发送。
踢设备 Admin 踢 A 的 device-1 device-1 在任意 Pod 的连接被关闭,其他设备保持在线。
全端退出 密码重置或封禁 A A 所有 Pod 上的连接关闭,旧 Token 后续登录失败。
Pub/Sub 瞬断补偿 人为断开 Pod-2 Redis 订阅后发送消息,再恢复并重连客户端 实时期间可能漏收,但重连后按 seq 补齐,最终状态正确。

11. 代码落点建议

IMServer:

  • server/realtime_dispatcher.go:统一跨 Pod 发布与订阅。
  • server/redis_fanout.go:改造成群消息 dispatcher 适配层。
  • server/sessionstore.go:补充按租户、用户、设备、Topic 的本地投递辅助方法。
  • server/session.go:登录、心跳、断线时刷新 Redis presence 和 control version 检查。
  • server/topic.go:个人/群消息发布成功后调用 dispatcher。
  • server/im_http.go:好友、系统通知、群治理、会话设置等 HTTP 写成功后调用 dispatcher。
  • server/kafka_admin_commands.go:Admin command 执行成功后调用 dispatcher。
  • server/store/group_message_runtime.go:补充 realtime bus Redis 发布、订阅、去重 key。

Admin Server / HotGo:

  • 后台写操作保留 command/outbox 到 Kafka 的可靠链路。
  • 不新增任何直接调用 IM Pod WS 内存态的接口。
  • 命令结果和摘要通过 Kafka result/event 回写 hotgo。
  • 后台实时页面如需刷新,使用 Admin 自己的 WebSocket/SSE,从 hotgo 命令结果或摘要表驱动,不复用 C 端 WS。

12. 风险与注意事项

  1. Pub/Sub 不保证离线重放,因此所有实时投递都必须有最终补偿来源。
  2. event_id 去重不能代替消息 client_mid 幂等;二者层级不同。
  3. 群消息 seq 只能有一个发号源,Redis 不可用时不能临时改用 MySQL 发号。
  4. Session control 不能只靠 Pub/Sub,必须在心跳/上行业务包检查版本兜底。
  5. Topic 权限变化后必须刷新内存快照和 Redis 缓存,否则多开端会出现权限漂移。
  6. Push payload 不得带敏感正文,只带 tenant_code/topic/seq/notification_id 等定位字段。
  7. Admin command 成功但实时投递失败时,不回滚业务状态;客户端通过重连或重新 GET 补偿。
  8. 跨租户 Redis key、channel、payload 都必须带 tenant_id,不允许使用裸 topic 做全局路由。