Table of Contents

P2P/gossip 架构(HyParView 会员 × 广播)

本文回答:P2P 模式怎么保证会员收敛、失效自愈与广播有界——HyParView 双视图会员维护、 JOIN 随机游走、φ 统计故障检测、失效替换与分区自愈、gossip 广播的去重/TTL/防环/防风暴。 它是 transport-architecture.md 的一个上层机制面 (spec-06 语义,spec-12 传输接线),与 raft 互斥并存(P2P 模式 = 最终一致性、无共识)。

与 net.md §8 的关系:net.md §8 是使用面(怎么挂载 P2P/广播、参数怎么调); 本文是实现面——据本文判断"会员怎么收敛、失效怎么检出、广播怎么不风暴不重复"。 代码引用为仓库相对路径(P2P/… = src/KernLab.Tier.Core.Net/P2P/…)。

规格源:spec-06(HyParView 设计规格——内部设计文档,语义已实施;本文为实施后公开描述)。


1. 定位与总览

                    PeerController(HyParView 会员管理器,578 行)
        ┌──────────────┬──────────────┬──────────────┬──────────────┐
        │ 活跃视图       │ 被动视图      │ φ 故障检测     │ 周期维护       │
        │ 对称邻居 ≤k(4)│ 备用池 ≤20   │ PhiAccrual    │ 心跳/φ/shuffle│
        └──────┬───────┴──────┬───────┴──────────────┴──────────────┘
               │ ActiveView   │
               ▼              ▼
        SwarmBroadcast(gossip 广播)   ── 数据报 0x02/0x05 → 传输层(UDP/TCP 回落)
  • 职责分离:PeerController 只管会员(视图/JOIN/心跳/φ/shuffle),不含广播;SwarmBroadcast 构造时经委托接 PeerController.ActiveView——广播目标域 = 活跃视图。
  • 定位(spec-06 §1):超大集群模式——O(k) 连接不随 N 增长、无 leader 压力、最终一致性; 与 raft(静态配置、强一致、O(N) 复制)互斥并存。P2P 模式下日志走 TierWAL 本地 append(无共识)。

2. HyParView 会员管理

2.1 双视图模型

视图 语义 边界 存储
活跃视图 对称活跃邻居(gossip 扇出/转发目标域) 有界 k(缺省 4) Dictionary<NodeId, long>(邻居 → 建立时间)
被动视图 非对称备用池(断连补位来源) 上限 20 LinkedList<NodeId>(头 = 最新;去重插头 + 超限裁尾)
  • 对称性不变量:本节点在 A 的活跃视图 ⟺ A 在本节点活跃视图(双向建立/移除,JOIN/NEIGHBOR 双向、DISCONNECT/FAIL 双向、挤掉通知、单向边超龄判死——见下)。
  • 不持久化:视图从配置 seed 重 JOIN 重建(分区全打散后的恢复路径——部署层提供种子, 不做零配置发现)。

2.2 JOIN 随机游走入组(PeerController.cs:138-166)

  1. 种子入活跃视图:seed 直接成为对称邻居(论文 JOIN 第一步);视图已满则挤掉最旧并通知。
  2. 发 JOIN 随机游走:TTL 缺省 6;每经一节点——Ttl==0 或本节点活跃视图有空位 = 终点 (建立活跃关系 + 回送 NEIGHBOR);否则新节点进沿途节点被动视图,再随机挑活跃邻居 Ttl-1 转发——转发目标随机,天然分散入网压力。
  3. 终点回送:等待 NEIGHBOR 应答(JoinWaitTimeout 5s 有界)。

收敛保证:

  • 应答丢失不阻塞:超时后种子已在活跃视图(会员关系已建立),视图收敛交给 shuffle/周期维护; JOIN 一次性消息不重发,丢包介质下由使用方周期重 JOIN 驱动。
  • 满视图有界:AddActiveLocked 满 k 则按建立时间挤最旧,净保持 k 有界。
  • 挤掉保对称:被挤者收到 DISCONNECT 通知(NotifyEvicted)——否则它单边持有本节点且收不到 心跳(无 φ 采样)→ 视图残留永不清理。锁外发送是刻意设计(InProcess 直排派发下同步递归 会重入 AddActiveLocked 造成视图超界——挤掉+插入原子完成后通知移锁外,任意递归链每次 add 均净 -1+1 = 有界)。

2.3 心跳探活与 φ 故障检测

  • 心跳节拍:周期循环(LoopAsync)由 DeadlineRegistry 订阅驱动(刻意不用 Task.Delay—— TimerQueue 高频窗口丢表项病理已实锤);写脉冲容量 1 DropOldest。
  • φ 判定(PhiAccrualDetector.cs):心跳到达间隔采样窗口(缺省 12 样本)拟合正态 (均值/σ,n-1 方差),φ = -log10(1-Φ((Δt-μ)/σ));缺省阈值 φ=8(误判率 ~10⁻⁸);样本不足 (<3)时 φ=0 冷启动宽限。
  • 防误判三件套:
    • 方差地板 σ = max(sqrt(variance), mean×0.3)(Akka 同款)——定期间隔进程内投递 σ≈0 时 单次调度抖动 z 即爆表(50ms 心跳 + 100ms 抖动会全员互判失败视图雪崩);地板使 φ=8 要求 dt ≳ 4× 心跳间隔——真断连检出、抖动不误判。
    • 锚定标志 _anchored——旧实现用 _samples.Count>0 判定,首样本入队前计数仍 0 → 永假 永不采样 φ 恒 0 楔死,已修。
    • 单向边判死 IsZeroSampleExpired(心跳×8)——对方不认为我是邻居则永不回发心跳,我方零 样本恒 φ=0,无此判定则视图悬挂占坑;宽限 = 心跳间隔×8 覆盖调度抖动。

2.4 失效替换与 FAIL 扩散

  • 判定失败 → 先移出被动池(防补位立刻拉回)→ 移出活跃视图 → RefillFromPassiveLocked 补位:被动视图头(最年轻)优先升入活跃视图 + 发 NEIGHBOR 对称建立;补位先快照候选再发送 (直排介质同步进入对端 handler 可能重入变异被动视图)。
  • FAIL 扩散:本地检出后向其余活跃邻居广播 FailMsg——故障信息全网快速传播,不等 φ 逐跳 自行发现;对端同规则移出+补位。
  • 主动离开:LeaveAsync 下电前向全部活跃成员发 DISCONNECT(尽力语义,逐成员隔离)。
  • 对端离线兜底:_transport.PeerGone 事件(传输层发现连接断开)触发同样的移除+补位路径。

2.5 shuffle 搅动与分区自愈

  • shuffle(周期 60s):从活跃视图随机挑目标,发送被动视图前 PRWL(缺省 6)个样本;接收方 并入被动视图——被动池持续混合,成员搅动下新节点信息全网渗透。
  • 分区自愈:失效 φ 检出 → 补位 + FAIL 扩散;被挤节点 DISCONNECT 主动移除;单向边超龄判死; 全组打散 = 视图不持久化 + 配置种子重 JOIN 重建。
  • 周期循环必活:LoopAsync 捕获全部异常只记日志继续——心跳/φ/shuffle 全在此循环, 静默死亡 = 视图悬挂永不自愈。

3. gossip 广播(SwarmBroadcast)

3.1 语义与帧

  • 语义(spec-06 §6):沿活跃视图扇出、逐跳转发,最终送达;不保证顺序、不保证送达 (尽力数据报)。要可靠走 raft/请求回调。
  • 帧 GossipFrameHeader 37B:[MsgId 16B][Origin 16B][Ttl 1B][PayloadLength 4B] + payload。
    • MsgId = 广播身份(16B 随机),去重键——同消息全网至多投递一次。
    • Origin = 最初广播者,中继逐跳不变(投递面语义——订阅方拿到的 NodeId 是最初广播者)。

3.2 防重复 / 防环 / 防风暴(三重约束叠加)

约束 机制
防重复 SeenCache(MsgId LRU,有界缺省 4096)——同 MsgId 至多投递一次;源头自登记(环回副本/自播路径归一)
防循环 TTL 有界(缺省 4,每中继减一,Ttl<=1 不再转发)+ 转发排除来源者(SampleTargets 的 exclude)
防风暴 去重 + TTL 减尽即停 + fanout 上限(缺省 = 整个活跃视图 ≤k;扇出不随节点数放大——每跳至多 k 条)
  • 重复窗口有界性:重复窗口 = 去重容量 × 传播时延。
  • 畸形防护:载荷超限 60KB 源头 fail-fast 抛(不静默丢失);入站畸形(负长度/截断)静默丢弃; handler 快进快出契约(慢回调计数兜底)。

3.3 扇出与抽样

  • 发送 Send:TaskSink.SubmitFast 尽力发送(fire-and-forget 受控形态——Dispose 有界排空)。
  • 目标抽样 SampleTargets:活跃视图内去掉自身与来源者;候选数 ≤ Fanout 全取,超出则 partial Fisher-Yates 随机抽前 Fanout 位——转发目标随机,多路径冗余 + 重叠小世界漫游。
  • Fanout 缩小 = 牺牲送达广度换发送量。

4. 活性 / 收敛 / 有界性清单

保证 机制
无中心引导 部署层种子 + JOIN 随机游走扩散,无发现服务器(不做零配置发现)
活跃视图有界收敛 满 k 挤最旧 + 锁外通知保对称(净 -1+1 有界)
对称性不变量 JOIN/NEIGHBOR 双向建立;DISCONNECT/FAIL 双向移除;挤掉通知;单向边超龄判死
失效检出限时 每 peer 独立 φ 采样 + 心跳驱动(φ=8 限时检出、抖动不误判)
失效替换 被动视图年轻者优先补位 + FAIL 扩散全网
成员搅动收敛 周期 shuffle 交换被动视图(PRWL=6 样本)
分区自愈 视图不持久化、配置种子重 JOIN;周期循环必活(异常隔离继续)
广播无环/无风暴/无重复 MsgId LRU 去重(源头自登记)+ TTL 逐跳 + 转发排源 + fanout 随机抽样
广播有界 载荷 60KB fail-fast、去重容量 4096 LRU、TTL/fanout 上限

5. 与传输层的接线(spec-12)

  • 协议域:ProtocolIds.HyParView=0x02、ProtocolIds.Gossip=0x05(内部核心区 0x00-0x4F)。
  • 挂载口:ICoreProtocolPort.RegisterCoreProtocol(protocolId, handler, DatagramBearer.Udp)—— 机制零特权(与使用方同一分发路径);装配要求传输实现内部口,否则构造抛。
  • 分发:DatagramDispatcher——未注册协议静默丢弃 + 钩子;handler 异常隔离(不外泄不致命)。
  • 承载:声明 UDP 的协议域在对端 UDP 端点已知、载荷 ≤1200B 时走 UDP;否则回落 TCP 帧流 承载(尽力语义不变)。Gossip 帧含认证标签(KeyPair 档 32B 尾标签)。
  • 消息线格式:六类消息(JOIN/NEIGHBOR/DISCONNECT/FAIL/SHUFFLE/HEARTBEAT),[Tag 1B] + 字段声明序;发送方身份不上线(分发即身份);解码防御逐字段生成(未知 tag/截断/Count 超上限 = 丢弃,已知 tag 尾部扩展容忍前向兼容)。

6. 与 raft 的关系(并存语义)

  • 互斥并存:P2P 模式(HyParView + gossip + TierWAL 本地 append)与 raft 模式(静态配置 + 复制共识)是两套独立语义——P2P 是最终一致性/超大集群,raft 是强一致/有限成员。
  • SwarmSync 消费 HyParView 成员关系(多源快照的分发拓扑);raft 不依赖 HyParView (静态配置成员)。同节点 raft/P2P 双跑的完整语义仍在归档 spec-06 §8 待决点。

7. 相关文档

  • 使用面(挂载/参数/观测):net.md §8
  • 网络层架构(三形态/寻址/安全——P2P 的传输接线):transport-architecture.md
  • 规格源:spec-06(内部设计文档,不随包发布——本页为实施后公开描述)。
  • 成员数定则(P2P 何时用):net.md §3.5