尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
WuKongIM Delivery Tag 投递标签机制:leader 权威的频道订阅者分片快照设计与实现
即时通讯后端【免费下载链接】WuKongIMMore than just IM 不只是即时通讯(IM)项目地址https://gitcode.com/gh_mirrors/wu/WuKongIM点击查看免费下载Delivery Tag 是 WuKongIM 为全频道实时投递设计的一套统一成员快照机制由频道 leader 节点创建并维护按目标节点分片的订阅者快照其他节点只能按tagKey拉取自己的分片并缓存从而避免每条消息都分页扫描订阅者存储。本文以 delivery-tag 实现计划 与配套的 delivery-tag 设计文档 为主体结合当前仓库的源码证据完整讲解该机制的背景、数据模型、投递与失效流程、缓存一致性策略以及六阶段实施步骤与验证命令读完即可掌握该方案的设计全貌与落地方法。一、背景为什么要引入 Delivery Tag当前实时投递链路已经解决了群消息按订阅者写多份消息的落盘问题但所有频道在实时投递时仍然存在两个热路径成本每条消息都需要分页读取订阅者并按 UID 查询 presence 路由如果尝试维护channel - online routes索引又会把在线/离线高频变化扩散到每个频道或每个节点节点状态和实时更新成本都很高。因此方案参考旧版 WuKongIM 的 tag 机制把频道成员集合统一做成由频道 leader 管理的投递快照。注意一个关键边界tag 只描述频道订阅者按目标节点的分片不描述实时在线连接在线状态仍由投递时的 presence / 本地会话状态判断。该机制适用于所有频道——小频道、普通群、临时频道、cmd 频道和大频道都在覆盖范围内。10 万人频道只是最需要它降低重复扫描成本的高压代表场景用来验证方案上限和压力保护而不是唯一适用范围参见 100k 频道支持风险分析。二、核心结论与关键约束Delivery Tag 的定义频道 leader 创建和更新的、按节点分片的订阅者快照其他节点只能按 tagKey 拉取自己的分片并缓存。实施计划中明确列出的约束引自 实施计划 的 References And ConstraintsDelivery tags 对所有频道都是 leader-authoritative不局限于 10k/100k 大频道普通成员变化保持tagKey稳定、只推进tagVersion非 leader 缓存只能包含本地分片绝不能包含完整订阅者集合旧请求绝不能驱逐新的本地 tag refSubscriberMutationVersion必须持久化且权威缓存只消费它、不自行生成PartitionTopologyVersion必须比较完整的{HashSlotTableVersion, SlotAuthorityRefs[]}形状而不是取单个 max versioncmd / 派生投递 tag 也必须携带SourceChannelKey和SourceSubscriberMutationVersion当源频道前进时必须变 staleleader incarnation 变化、leader 重启或冷缓存重建可以铸造新的tagKey只有普通成员变更必须保持当前tagKey稳定。三、数据模型设计3.1 核心结构设计文档给出了建议的逻辑结构命名可在实现阶段调整// DeliveryTag is a leader-owned subscriber snapshot for one channel. type DeliveryTag struct { Key string ChannelID string ChannelType uint8 Nodes []DeliveryTagNode CreatedAt time.Time LastAccess time.Time TagVersion uint64 SubscriberMutationVersion uint64 PartitionTopologyVersion PartitionTopologyVersion } // DeliveryTagNode stores the subscriber partition that one node should process. type DeliveryTagNode struct { NodeID uint64 UIDs []string SlotIDs []uint32 } // PartitionTopologyVersion describes the cluster-controlled partitioning state // that affects UID ownership. type PartitionTopologyVersion struct { HashSlotTableVersion uint64 SlotAuthorityRefs []SlotAuthorityRef } // SlotAuthorityRef records the authoritative owner/version for one UID slot // represented by this tag. type SlotAuthorityRef struct { SlotID uint64 LeaderNodeID uint64 ConfigEpoch uint64 BalanceVersion uint64 }3.2 tagKey 与 tagVersion 的语义分工tagKey是tag incarnation ID不是 channel ID。首次创建 tag 时由频道 leader 生成普通成员 Add/Remove/Reset/RemoveAll不改变 tagKey。tagVersion是同一个 tag incarnation 的公开防护版本。普通成员变化、SubscriberMutationVersion前进、或PartitionTopologyVersion变化导致的重建都必须递增tagVersion只有当前tagVersion才能代表当前完整的 tag materialization。leader incarnation 变化、leader 重启导致内存 tag 丢失、TTL 后冷重建等场景可以生成新的 tagKey避免新 leader 用相同 key 和较小 version 命中 follower 旧缓存。SubscriberMutationVersion是成员存储的权威变更版本tag 复用前必须先比较它是否落后于存储侧的最新版本。follower 缓存命中必须同时匹配tagKey、tagVersion和PartitionTopologyVersionSubscriberMutationVersion只用于判断快照是否已经过时。3.3 SubscriberSource不同频道的订阅者来源Delivery Tag 适用于所有频道但不同频道的订阅者来源不同构建 tag 时不能把所有频道都简单地读 subscriber store。设计文档将解析逻辑抽象成SubscriberSource小接口频道类型订阅者来源个人频道Person从 channelID 派生双方 UID必要时先去掉 cmd 包装再解析原频道的两个 UIDAgent 频道从 channelID 派生用户 UID 和 Agent UID与个人频道同属 ID 派生型群 / 社区 / 话题 / 数据 / 直播 / AgentGroup分页读取 ordinary subscriber storeInfo分页读取 ordinary subscriber store并合并当前生效的临时订阅者 overlayTemp读取临时频道订阅者来源若是在线命令类临时投递则使用请求中明确给出的 UID 集合作为一次性、消息作用域的输入不写入 channel-level tag cache也不跨请求复用Visitors / CustomerService分页读取 customer-service 订阅者来源并把 visitor UID 从 channelID 中补入结果CustomerService 是旧兼容路径cmd / derived 频道先解析原频道再用原频道的 SubscriberSource 生成 tagcmd 频道本身不是独立成员集合抽象成小接口的目的是避免在 access 层或 tag manager 中写死入口协议逻辑对于实现尚未显式覆盖的 overlay必须在 source 层补齐而不是在投递路径末尾临时拼接。3.4 leader 缓存与 follower 缓存频道 leader 节点缓存完整 tagtagKey - DeliveryTag与channelKey - currentTagRef。currentTagRef至少包含{tagKey, tagVersion, SubscriberMutationVersion, PartitionTopologyVersion}用于判断一次投递请求比本地缓存更新还是更旧。leader 需要完整 tag因为它要知道分发到哪些目标节点也要响应 follower 的GetTag(tagKey, tagVersion, targetNodeID)请求。非 leader 节点只缓存本节点分片语义上是 local partition tag不是频道全量 tagtagKey - {tagVersion, SubscriberMutationVersion, PartitionTopologyVersion, local UIDs}与channelKey - currentTagRef。这样任何频道都不会在每个节点复制全量 UID 快照大频道收益最明显。3.5 分片维度分片建议沿用旧 WuKongIM 思路按 UID 所属的权威节点分片如 UID slot leader / presence authority owner。原因tag 是成员集合缓存而非在线路由缓存用户连接实际在哪个接入节点仍由 presence 判断按 UID 权威节点分片后每个节点只处理自己负责的 UID 子集避免所有节点遍历全量成员。四、投递流程4.1 频道 leader 获取或创建 tag消息提交后进入实时投递频道 leader 执行根据(channelID, channelType)查channel - currentTagRef若存在 tagKey读取tagKey - DeliveryTag并校验tagVersion、SubscriberMutationVersion、PartitionTopologyVersion若 tag 不存在、过期、SubscriberMutationVersion落后或PartitionTopologyVersion任一分量落后则 leader 通过 SubscriberSource 解析订阅者并重建 tag重建时按 UID 权威节点聚合得到DeliveryTag.Nodesleader 写入本地缓存首次创建时生成 tagKey后续成员变更只递增 tagVersionleader 把tagKey和tagVersion附到本次投递事件或节点间投递请求中。只有这一步允许创建频道 tag。4.2 leader 按 tag 分发到目标节点ACK/retry 所有权下沉leader 不再为远端节点回显所有 routes只需要根据 tag 的节点分片把消息、tagKey、tagVersion分发到对应节点。tagKey tagVersion是 leader - target 的公开 fenceSubscriberMutationVersion和PartitionTopologyVersion只存在于 tag 内部 currentTagRef 中任何会改变它们的重建都必须同时推进tagVersion避免外部 wire contract 变成四字段协议。每个目标节点收到的数据尽量精简为channelID、channelType、tagKey、tagVersion、消息 frame / metadata、必要的 trace / retry metadata。若目标节点就是 leader 本地节点则直接进入本地分片投递。关键设计ACK / retry 所有权从频道 leader 下沉到目标分片节点。leader 只负责把消息、tagKey、tagVersion 和目标分片任务交给对应节点不维护该分片内每条 route 的 ACK/retry 状态目标分片节点获取本地 UID 分片后解析在线 routes并作为这些 routes 的 delivery owner 维护 ACK、retry、session close 和过期清理如果实际连接在其他 gateway 节点目标分片节点继续使用现有 route-baseddelivery_pushRPC 转发该 RPC 仍使用accepted count retryable/dropped 精确 routesleader - 目标分片节点这一层只返回分片任务状态如accepted、retryable、tag_mismatch、not_leader不要求目标节点回传所有 accepted routes分片任务按(channelID, channelType, messageID, targetNodeID, tagKey, tagVersion)幂等处理避免 leader 重试或 committed replay 产生不可控重复投递。4.3 follower 获取本地分片并缓存目标节点收到带 tagKey / tagVersion 的投递请求后查本地channel - currentTagRef若请求与本地 current ref 完全一致且本地 tag body 存在、未过期直接复用若 tagKey 相同但请求 tagVersion小于本地 tagVersion当作 stale event 处理不允许删除或降级本地新缓存若 tagKey 相同但请求 tagVersion 更新或SubscriberMutationVersion/PartitionTopologyVersion不一致说明 local partition 失效需向当前频道 leader 拉取若 tagKey 不一致follower 不能根据随机 key 本地判断新旧也不能先删除 current ref必须向当前频道 leader 确认当前 ref拉取请求为GetTag(channelID, channelType, tagKey, tagVersion, targetNodeIDlocalNodeID)leader 必须返回当前 channel ref 或明确返回tag_not_current/not_leaderleader 只返回当前请求节点分片的 UIDs 和当前 version若该节点没有分片返回空集合也可以缓存结果不能被解释为频道全量订阅者follower 只在 leader 确认返回 ref 是当前 channel ref 时才更新本地 cache旧响应必须丢弃。follower 在任何情况下都不能通过本地订阅者 store 自行创建频道 tag也不能基于本地 tag 对外提供频道全量订阅者语义。4.4 目标节点执行本地实时投递目标节点拿到本地 UIDs 后按 UID slot / presence authority 分组后批量查询 presence / 本地会话不能假设一个本地分片只包含单个 slot过滤离线用户、发送者、黑名单等规则对在线 endpoint 写连接或转发到实际接入节点对 accepted routes 建立 ACK / retry 状态。这一层仍可能面对大量在线连接因此仍需保留 inflight、ACK、retry、RPC chunk 等既有保护。五、成员变更流程成员变更必须收敛到频道 leader 更新 tag。5.1 Add / RemoveAPI / usecase 把成员变更提交给权威 subscriber storesubscriber rows 写入和SubscriberMutationVersion递增必须在同一个 slot Raft command / 同一权威存储事务中完成store 返回本次逻辑变更后的SubscriberMutationVersiontag manager只能消费这个版本不能自己生成找到频道 leader请求在 leader 本地则直接更新否则通过节点 RPC 请求 leader 更新若 leader 上不存在当前 channel tag记录本次成员变更已影响 tag下一条消息按最新 SubscriberSource 重建若 leader 上存在当前 tagadd 把新增 UID 合并到对应节点分片remove 从对应节点分片删除 UID使用 store 返回的SubscriberMutationVersion并递增一次 tagVersion更新channel - currentTagRef旧 version 的本地分片缓存等待目标节点收到新 version 后失效或由 TTL 清理。5.2 Reset / RemoveAllReset / RemoveAll不建议在 tag 中做逐 UID 删除——频道越大逐项删除越容易形成 CPU 和锁持有尖峰。推荐策略leader 上存在当前 tag直接重置/清空 tag 的节点分片并递增 tagVersion不生成新的 tagKeyleader 上不存在当前 tag不为了 Reset/RemoveAll 单独创建 tag下一条消息首次构建 tag 时再生成 tagKeyReset Add 等复合操作按一个逻辑成员操作发布一个SubscriberMutationVersion和一个 tagVersion存储层分 chunk 写入时不应每个 chunk 暴露一个新 version避免投递看到中间态。5.3 成员变更失败补偿SubscriberMutationVersion必须持久化在权威 slot store 的频道成员状态中leader 重启或切换后仍能读取成员变更请求到达 leader 后leader 应先标记该 channel tag dirty再提交权威 subscriber store mutation成功后用返回版本更新 tag更新完成清除 dirty非 leader 节点的 RPC 更新失败时可记录 pending invalidate / retry 任务但它只是加速信号不能作为唯一一致性保障leader 处理下一条消息前必须先比较权威SubscriberMutationVersion落后则从 SubscriberSource 重建并把新版本写回缓存分 chunk 写入必须共享同一个逻辑SubscriberMutationVersion不允许仅依赖 TTL 修复成员变更后的旧完整 tagTTL 只能作为兜底资源回收。5.4 cmd / 临时频道其 tag 创建权属于对应频道 leader其他节点只能请求和缓存cmd / derived tag 必须记录SourceChannelKey和SourceSubscriberMutationVersionderived 频道 leader 每次使用 tag 前都要读取源频道当前SubscriberMutationVersion版本不同则直接判定 stale 并重建正确性由源版本比较保证初版不依赖持久反向索引可维护本地sourceChannelKey - derivedTagKey set作为主动清理优化但 leader 切换或索引丢失后仍靠源版本比较兜底若源频道 leader 与 derived 频道 leader 不在同一节点源成员变更只需保证权威源版本已递增主动 invalidate RPC 是加速路径而非正确性前提。六、缓存一致性策略6.1 tagKey tagVersion 是主要一致性信号目标节点收到 leader 分发的消息后必须用请求里的tagKey tagVersion与本地缓存做双校验请求比本地 current ref 旧判定为 stale event不能触发本地 current ref 的删除或回退tagKey 不一致只能按当前请求对应的 key 单独拉取或等待 leader 指示不能无条件覆盖 channel 维度 current ref通常只发生在首次创建、leader incarnation 变化、TTL 清理后冷重建等场景tagKey 一致但 tagVersion 不一致本地缓存已过期只有请求比本地 current ref 更新时才允许替换旧请求不能驱逐新缓存tagKey、tagVersion、SubscriberMutationVersion、PartitionTopologyVersion都一致才允许直接使用本地缓存的 UID 分片旧投递事件携带旧 tagVersion 可继续尽力投递若 leader 已无法返回该版本按 retry/fallback 处理。普通成员变化必须保持 tagKey 稳定、只递增 tagVersion确保目标节点通过 version mismatch 识别缓存失效并重新拉取。6.2 PartitionTopologyVersion 处理分片拓扑变化tag 记录创建时的PartitionTopologyVersion它不是单一本地计数而是由控制面/集群运行时暴露的权威引用集合HashSlotTableVersion全局 hash-slot table 版本来源是现有cluster.HashSlotTableVersion()SlotAuthorityRefs本 tag 中所有 UID 所属 slot 的有序列表每项{SlotID, LeaderNodeID, ConfigEpoch, BalanceVersion}LeaderNodeID来源是cluster.LeaderOf(slotID)presence authority RPC 按 UID slot leader 路由因此 presence authority owner 被包含在该字段中ConfigEpoch/BalanceVersion来源是 controller 的SlotAssignment某 slot 取不到 assignment 版本时不能把 0 当作命中只能保守重建或返回 retryable。tag 覆盖多个 UID slot 时不能只取 max version必须记录排序后的SlotAuthorityRefs比较时要求HashSlotTableVersion相等、slot 集合相等、每个 slot 的LeaderNodeID/ConfigEpoch/BalanceVersion都相等。tag 构建必须从单个权威控制面快照读取这些字段不能混用旧 hash-slot table version 新 assignments的分段读取结果读不到自洽快照就必须重试或返回 retryable。6.3 TTL 处理冷频道和漏清理每个 tag 维护LastAccess后台 bucket 定期扫描超过WK_DELIVERY_TAG_EXPIRE后删除 tag若channel - tagKey指向的 tag 已不存在也删除该 channel 映射。TTL 只负责资源回收不保证业务实时一致性。6.4 leader 切换频道 leader 切换后旧 leader 上的 tag 不再权威。新 leader 首次处理该频道消息时重新创建 tag incarnation 并生成新的 tagKey若权威 store 上的SubscriberMutationVersion已更高直接以 store 版本重建不沿用旧缓存。节点请求 tag 时若发现目标不是当前 leader返回not_leader或由调用方重新定位 leader。follower 缓存中的旧 tag 可因 tagKey 不匹配、tagVersion 不匹配、SubscriberMutationVersion落后、PartitionTopologyVersion变化、TTL 或请求失败而失效。七、失败处理follower 获取 tag 失败对实时投递返回retryable而不是本地扫描订阅者创建 tagdelivery runtime 按已有重试机制退避重试多次失败后可降级为本次实时投递 dropped但消息本身仍已在 channel log 中持久化。leader 上 tagKey 或 version 不存在可能原因leader TTL 清理了旧 tagleader 切换后旧 tagKey 不存在或新 leader 只保留同一 tagKey 的更新版本follower 收到的是旧事件tagKey 存在但请求 version 与 leader 当前 version 不一致。处理策略leader 不应强行用旧 key/version 创建旧快照返回tag_not_found或tag_version_mismatch由调用方重试并重新获取当前 channel tag若权威SubscriberMutationVersion已前进而请求携带旧版本leader 应优先返回当前版本信息而不是把旧请求写回 current ref。成员变更时 leader 不可用成员存储变更成功但 leader tag 更新失败时不回滚订阅者存储不允许只删除本地可见 tag 映射后结束必须依赖权威存储里已递增的SubscriberMutationVersion作为最终失效凭据pending invalidate / retry 只负责尽快把新版本送到 leader。leader 恢复后下一条消息使用 tag 前必须先比较权威SubscriberMutationVersion能增量更新则递增 tagVersion无法确认增量完整性则从 SubscriberSource 重建。八、与在线索引方案的对比优点不需要在线/离线时更新大量频道索引不需要每个节点维护全频道在线成员集合不把连接 churn 放大成频道维度的状态 churn关注点清晰tag 管成员快照presence 管在线状态leader-only 创建语义清晰避免多节点并发创建不一致快照follower 只缓存本地分片内存不会按节点数 × 全量成员放大成员快照可跨多条消息复用显著减少热门频道重复扫描订阅者存储tagVersion 递增做缓存失效、tagKey 保持稳定目标节点只需按 version mismatch 重新拉取可与现有 ACK / retry / RPC chunk / subscriber 分页能力兼容演进ACK/retry owner 明确下沉到目标分片节点。缺点与风险每条消息仍需要对 tag 分片内 UID 做在线判断频道成员全部离线时仍可能遍历该频道的 UID 内存分片只是避免了存储扫描和跨节点全量 presence RPCtag 创建和 miss 请求会集中到频道 leader热点或大频道 leader 压力更高成员频繁变化时 tagVersion 频繁递增follower cache 命中率下降成员极多但消息很少的频道首次投递有一次明显的 tag 构建成本tag 是最终一致缓存不保证成员变化后所有旧事件立刻停止使用旧成员快照tag 只优化成员解析不解决大量在线连接的写放大、ACK 索引和慢连接问题UID 分片节点与实际连接节点不同时目标分片节点仍需按 presence authority 分组查询并二次转发。九、六阶段实施计划关联文档主体实现计划以internal/app/deliveryrouting.go为编排点但不再把订阅者列表当作每条消息的全频道扫描新增internal/runtime/deliverytag缓存负责 tag incarnation、tagVersion、TTL 与 stale-safe 失效internal/usecase/delivery把各频道订阅者来源转成 leader 构建或拉取的 tag 分片权威订阅者存储在频道记录上持久化SubscriberMutationVersion使 tag 失效由存储状态围栏fence而非缓存本地启发式。技术栈为 Go、testingtestify、现有pkg/slotRaft/meta 栈、pkg/cluster、internal/access/node、internal/runtime/delivery、internal/usecase/delivery。同时遵循 AGENTS.md 的分层边界不新增大而全的 service 层关键导出类型和方法补简洁英文注释。Task 1在 slot meta 中持久化 SubscriberMutationVersion修改pkg/slot/meta/catalog.go、channel.go、codec.go、batch.go、pkg/slot/fsm/command.go、command_inspection.go、pkg/slot/proxy/store.go配套测试channel_test.go、codec_test.go、state_machine_test.go、subscriber_command_limits_test.go。先写失败测试证明metadb.Channel可往返持久化SubscriberMutationVersion订阅者 add/remove/reset 路径保持版本单调递增分块逻辑订阅者变更只向调用方暴露一个版本围栏既有订阅者命令大小限制仍拒绝超长 UID 集合。示例测试名func TestChannelSubscriberMutationVersionRoundTrip(t *testing.T) { /* ... */ } func TestSubscriberMutationVersionAdvancesMonotonically(t *testing.T) { /* ... */ }运行go test ./pkg/slot/meta ./pkg/slot/fsm -run Test.*SubscriberMutationVersion|Test.*SubscriberCommandLimits -count1随后实现最小元数据变更给 channel 记录增加SubscriberMutationVersion在 value codec 中编解码slot FSM/store 写路径在普通 channel 更新时保留它、仅在订阅者成员变更被持久化应用时推进它。Task 2构建 Delivery Tag 运行时缓存新建internal/runtime/deliverytag/types.go、cache.go、manager.go、topology.go与manager_test.go。测试证明leader 构建的 tag 在普通 Add/Remove 下保持同一tagKey、只递增tagVersionfollower 只缓存本地节点分片旧 version 的 stale 请求永不驱逐新缓存条目PartitionTopologyVersion不匹配强制 refetch/rebuildTTL 清理冷 tag 与失效 channel refcmd/derived tag 在源频道SubscriberMutationVersion前进时变 staleleader incarnation 变化或冷重建可铸造新tagKey且不与旧 follower 缓存条目冲突。示例func TestTagVersionIncrementsWithoutChangingKey(t *testing.T) { /* ... */ } func TestStaleRequestDoesNotEvictNewerLocalTag(t *testing.T) { /* ... */ } func TestLeaderIncarnationCanMintFreshTagKey(t *testing.T) { /* ... */ }go test ./internal/runtime/deliverytag -run Test.*Tag|Test.*Topology -count1节点内 manager 职责跟踪tagKey - tag body、channelKey - current reffollower 只存本地分片复用前比较完整分区拓扑引用用 stale-safe 替换规则保证旧查找不覆盖新 refcmd/derived tag 携带SourceChannelKey与SourceSubscriberMutationVersion并在源版本前进时标记 stale允许新 leader incarnation 或冷重建铸造全新tagKey。Task 3让投递订阅者认识 tag source 与特殊频道类型修改internal/usecase/delivery/subscriber.go、新建source.go、修改subscriber_test.go、internal/usecase/channel/app.go必要时修改internal/usecase/user/legacy.go。测试证明 subscriber source 能解析派生 ID 的个人频道派生 ID 的 Agent 频道普通群/社区/话题/数据/直播/AgentGroup 的 paged subscriber 存储带临时 overlay 合并的 Info 频道不把一次性请求 UID 变成可复用 channel 级 tag 状态的 Temp 频道Visitors 与客服频道cmd/derived 频道通过解析源频道并复用其 sourcecmd/derived tag 在源频道订阅者版本前进时变 stale。示例func TestSubscriberSourceResolvesPersonChannel(t *testing.T) { /* ... */ } func TestSubscriberSourceRejectsReusableTempRequestSnapshot(t *testing.T) { /* ... */ }go test ./internal/usecase/delivery -run Test.*Subscriber|Test.*Snapshot -count1实现上把 resolver 拆成三块store-backed 订阅者的通用分页接口derived / temporary / overlay / legacy 频道的按类型小适配器含显式的 person 与 agent ID 派生 resolver一条能不暴露 access 层细节就把 source 交给 delivery tag manager 的路径。Task 4新增 leader-only 的 Delivery Tag RPC 并完成 wire contract修改internal/access/node/options.go、service_ids.go、client.go、delivery_push_codec.go、delivery_push_rpc.go新建delivery_tag_codec.go、delivery_tag_rpc.go与delivery_tag_rpc_test.go。测试证明只有频道 leader 能创建/更新 tagfollower 只能为某个 tagKey 拉取自己的本地分片响应携带tagKey tagVersion作为公开 fence不匹配或 stale 的响应返回可重试状态而不是覆盖新缓存delivery-push 响应仍精确报告retryable/dropped路由accepted在批量路径上可以是计数拓扑快照获取在无法一次性读到自洽PartitionTopologyVersion时 fail-closed 返回 retryableleader incarnation 变化或冷重建可返回新tagKey客户端必须把旧 key 视为 stale 而非强制复用。示例func TestDeliveryTagRPCRejectsNonLeaderUpdates(t *testing.T) { /* ... */ } func TestDeliveryPushBatchCanReturnAcceptedCount(t *testing.T) { /* ... */ }go test ./internal/access/node -run Test.*DeliveryTag|Test.*DeliveryPush -count1RPC contract 使用显式结果名ok、not_leader、tag_not_current、tag_version_mismatch、retryable。新增独立 tag RPC service ID在options.go注册 handler加 client helpercodec 只编解码 leader fetch/update 与本地分片缓存刷新所需数据保持旧 delivery-push codec 兼容新批量路径使用accepted count retryable/dropped 精确 routes。Task 5重接投递路由消费 tag 并交接所有权修改internal/app/build.go、app.go、deliveryrouting.go、presenceauthority.go测试deliveryrouting_test.go、multinode_integration_test.go、deliveryrouting_benchmark_test.go。测试证明消息通过 tag 分片解析而非每次扫描全量订阅者leader 只发送tagKey tagVersion加目标分片任务目标分片节点拥有本地 route 的 ACK/retry远端推送仍对大路由列表分块并保留精确retryable/dropped反馈stale tag 响应强制 refetch 而不是污染新缓存分片交接任务在(channelID, channelType, messageID, targetNodeID, tagKey, tagVersion)上幂等重试与 committed replay 不重复投递所有权。示例func TestDeliveryRoutingUsesTagPartition(t *testing.T) { /* ... */ } func TestDeliveryRoutingRejectsStaleTagResponse(t *testing.T) { /* ... */ }go test ./internal/app -run Test.*DeliveryRouting|Test.*Tag -count1实现要点在build.go构造 tag manager 并注入投递 subscriber 路径通过presenceauthority.go或小 helper 注入权威拓扑读取器使 manager 能比较完整分区版本而不本地猜测deliveryrouting.go一次解析 tag、按目标节点分发分片、保留远端delivery_pushroute 反馈契约、保留大路由分块保护并按六元组对分片交接工作去重。Task 6完成频道管理失效与端到端回归修改internal/usecase/channel/app.go、internal/app/channel_management_integration_test.go必要时修改user_management_integration_test.go。测试证明Add/Remove/Reset/RemoveAll 整个操作只推进一个逻辑订阅者变更围栏leader 在持久化变更路径之前/同时把 tag 标记 dirty 或失效后续投递请求在持久化版本前进后必须 refetch旧版 HTTP channel-management 端点仍可用dirty 生命周期显式写前标记 dirty持久化版本成功后清除或刷新 ref绝不只靠 TTL。示例func TestChannelManagementInvalidatesDeliveryTagOncePerLogicalMutation(t *testing.T) { /* ... */ } func TestChannelManagementLegacyEndpointsStillWork(t *testing.T) { /* ... */ }go test ./internal/app -run Test.*ChannelManagement|Test.*Legacy.*Management -count1实现要点频道管理 usecase 把分块写入视为一次逻辑变更只推进一次持久化订阅者变更围栏写前在 leader 侧标记 tag dirty持久化成功后清除或刷新 ref若实现过程中出现新的持久化不变量才更新 PROJECT_KNOWLEDGE.md。十、源码证据当前仓库中的落地痕迹实施计划 Task 1 的目标——让 SubscriberMutationVersion 持久化于权威存储——在当前仓库中可以找到对应实现pkg/db/meta/table_channel.go 的Channel结构体声明了SubscriberMutationVersion uint64注释明确tracks subscriber list mutation orderingpkg/db/meta/table_channel.go 的encodeChannelValue与 decodeChannelValue 将该版本字段纳入 channel value 的编解码保证其可持久化、可往返pkg/db/meta/subscriber_test.go 的TestSubscriberMutationVersionAdvancesAndInvalidatesChannelCache验证了低版本订阅者写入不会回退已有版本、高版本写入会推进版本、订阅者变更会清空频道缓存——与计划 Task 1版本单调、缓存失效的测试意图一致pkg/db/meta/batch.go 在批量合并时取max(next.SubscriberMutationVersion, existing.SubscriberMutationVersion)保证版本单调不回退pkg/slot/fsm/command.go 定义了tagSubscriberMutationVersion uint8 4的 TLV 字段标签并在 command.go 与 command.go 的编码/解码路径中使用说明 slot FSM 命令层面已具备持久化该版本的通道pkg/db/meta/inspect.go 将该字段暴露为subscriber_mutation_version供检查视图使用。另外计划与设计文档引用了旧版 WuKongIM 的 tag 机制实现作为行为参考learn_project/WuKongIM/internal/channel/handler/event_distribute.go、internal/manager/manager_tag.go、internal/manager/bucket.go等本方案在旧实现基础上做了三点演进成员变化不再RenameTag生成新 key改为保持 tagKey 稳定、递增 tagVersion旧NodeVersion扩展为完整PartitionTopologyVersion非 leader 节点依然只拉取本地 UID 分片而非全量 tag。十一、最终验证与仍需保留的保护最终验证实施计划给出的最终验证命令go test ./pkg/slot/meta ./pkg/slot/fsm ./internal/runtime/deliverytag ./internal/usecase/delivery ./internal/access/node ./internal/app -count1若 tag RPC 或 app 装配依赖多节点行为用-tagsintegration跑最小相关集成子集而不是全仓库结束前验证git diff --check通过计划记录显示 Task 6 的文档级 app 正则匹配不到单元测试其端点回归测试带 integration tag因此用如下命令验证最小相关子集go test -tagsintegration ./internal/app -run TestAppChannelManagementLegacyEndpointsPersistThroughStore|TestAppLegacyUserManagementEndpointsPersistAndQueryRuntime -count1仍需保留的保护即使引入 tag以下保护必须继续保留或加强单频道 inflight route 上限ACK binding 上限与超时清理远端 push chunk目标分片节点到实际连接节点的 retryable / dropped 精确路由反馈committed dispatcher 积压观测大频道 fanout 限流或降级策略leader - 目标分片节点的分片任务幂等与重试上限。结语Delivery Tag 方案是 WuKongIM 所有频道实时投递的统一成员快照机制它不追求维护精确在线索引而是把频道成员集合变成leader 权威、可复用、可失效的分片快照。该方案统一了小频道、普通群和大频道的投递成员解析路径显著减少热门频道重复读取订阅者存储和跨节点成员规划的成本同时避免每个节点维护大量在线订阅者索引。但它不能消除最终连接写入、ACK、慢客户端和 leader 热点问题因此应与现有投递背压、RPC chunk、ACK 清理以及后续 fanout job 方案配合使用。赞分享即时通讯后端【免费下载链接】WuKongIMMore than just IM 不只是即时通讯(IM)项目地址https://gitcode.com/gh_mirrors/wu/WuKongIM点击查看免费下载相关推荐WuKongIM Delivery Tag 投递方案深度解析Leader 权威分片快照如何化解大频道投递热路径成本WuKongIM Delivery Tag 投递方案深度解析Leader 权威分片快照如何化解大频道投递热路径成本 导读 本文围绕 WuKongIM 内部设计即时通讯后端WuKongIM 发送链路 CMD 频道语义收敛P2d-a/P2d-bcommand-style NoPersist 实时投递与订阅者 tag fence 加固实战WuKongIM 发送链路 CMD 频道语义收敛P2d a/P2d bcommand style NoPersist 实时投递与订阅者 tag fence即时通讯后端WuKongIM 发送权限管道实现方案Sender UID 权威路由、提交后 SENDACK 与接收方权威异步投递WuKongIM 发送权限管道实现方案Sender UID 权威路由、提交后 SENDACK 与接收方权威异步投递 本文围绕 WuKongIM 仓库中的实现计即时通讯后端上一篇Home Assistant 前端 UI/UX 就绪判定指南基于 ha-frontend-ux-readiness 技能的评审路由方法下一篇使用显式组件变体替代布尔属性next-shadcn-dashboard-starter 中的 React 组合模式实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

VSCode调用Keil编译:旧嵌入式工程的现代化编辑方案

VSCode调用Keil编译:旧嵌入式工程的现代化编辑方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/16 1:31:28
矩阵连乘问题:动态规划从原理到工程实践

矩阵连乘问题:动态规划从原理到工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/16 1:31:28
FFmpeg 转码 DASH/HLS 实战:从踩坑到生产部署

FFmpeg 转码 DASH/HLS 实战:从踩坑到生产部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/16 1:31:28
MORE NEWS

更多资讯

📰

ClaudeAnimationBase扩展指南:如何设计你自己的角色、新情绪与道具(附EMO注册方法)

【免费下载链接】ClaudeAnimationBase A starter kit for animating hand-painted cartoons with Claude: p5.js p5.brush, the Clawd character, 31 acted emotions and a guide for the model 项目地址: https://gitcode.com/gh_mirrors/cl/ClaudeAnimationBase …

📰

OpenGait框架更新解读:步态识别训练与部署实战指南

步态识别这几年在安防、异常检测、智能楼宇这些场景里越来越受关注,它最大的优势就是可以在远距离、被拍摄者不配合的情况下完成身份判断,这是人脸、虹膜这类近距离识别手段很难替代的。也正因为这个特性,步态识别对普通人来说可能还比较陌生…

📰

3 步免费换回 Win10 经典任务栏:ExplorerPatcher 安装使用教程

3 步免费换回 Win10 经典任务栏:ExplorerPatcher 安装使用教程 【免费下载链接】ExplorerPatcher This project aims to enhance the working environment on Windows 项目地址: https://gitcode.com/GitHub_Trending/ex/ExplorerPatcher 刚升级到 Windows 1…

📰

鸣潮后台自动战斗与自动刷声骸怎么做:ok-ww 使用指南

鸣潮后台自动战斗与自动刷声骸怎么做:ok-ww 使用指南 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸 一键日常 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-waves 核心关键词&#…

📰

REPENTOGON GridEntityDoor 扩展指南:门对象 PreviousState/PreviousVariant 修复与新 API 实战

游戏开发插件系统 【免费下载链接】REPENTOGON Script extender for The Binding of Isaac: Repentance 项目地址: https://gitcode.com/gh_mirrors/re/REPENTOGON 点击查看 免费下载 本文面向 The Binding of Isaac: Repentance 的模组开发者,系统讲解…

📰

技术创业路径拆解:从实验室到上市公司的关键阶段与避坑指南

“3位清华同学港股敲锣了”,最近在技术圈里刷屏。很多讨论把注意力放在“名校、年轻、财富”这些标签上,但作为一名开发者,我看到的不是钟声,而是另一件事:三位技术背景的创始人,把一家公司从代码做到了港股…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬