Table of Contents

TC.Tier.Core.Net 使用指南

网络与共识层:介质无关的节点通信三形态(数据报/请求回调/流式)× raft 共识引擎 × 对等机制(HyParView/Swarm),一个 NodeEndpoint 装配成型。

包:TC.Tier.Core.Net(依赖 TC.Tier.Core——执行/日志/可观测积木)。 深读积木拼装与反模式:../COORDINATION.md。 性能基线(loopback 形态面与 raft 引擎):perf/loopback-baseline.md


1. 快速上手

1.1 集群节点(成员制)

using TC.Tier.Core.Net;
using TC.Tier.Core.Net.Hosting;

var nodeId = NodeId.NewRandom();   // 或 NodeId.Parse("hex32...") / 身份源(见 §7)
await using var endpoint = await ClusterBuilder.Create(nodeId)
    .ClusterTag(0x74696572)                 // 集群归属——错集群握手期拒绝(fail-fast)
    .Listen("0.0.0.0", 9400)
    .Peers(new Dictionary<NodeId, IPEndPoint> { [other] = new(IPAddress.Loopback, 9401) })
    .WithLogger(logger)
    .StartAsync(ct);

1.2 直连客户端(地址制)

await using var client = await NetClientBuilder.Create(myId)
    .Connect("node1.example.com", 9400)      // 身份从握手得知
    .ClusterTag(0x74696572)                  // 拨入带标签的集群必须匹配(缺省 0)
    .RegisterRequestHandler(MyProtocolId, new MyHandler())   // 注册可先于拨号
    .StartAsync(ct);

NodeId? remote = client.RemoteId;            // 握手学得的对端身份

1.3 收发一条消息

// 数据报(尽力送达——丢失静默,协议自愈)
await endpoint.SendDatagramAsync(target, MyProtocolId, payload, ct);

// 请求回调(关联应答 + 超时)
byte[] resp = await endpoint.SendRequestAsync(target, MyProtocolId, ask,
    new RequestOptions { Timeout = TimeSpan.FromSeconds(2) }, ct);

2. 概念

术语 含义
节点身份 NodeId 协议一等标识——16B 不透明字节串(NewRandom/NewSequential/Parse/hex32 文本;Empty 哨兵)
协议域 protocolId 端点多路复用的字节标识。核心区归机制面;注册区 0x60-0xAF 归使用方自管ProtocolIds.IsUserRegistrable 校验,同号重复注册抛)
介质 消息的实际承载:InProcess(同进程直排——测试主场)/ TCP(生产跨进程)/ UDP(数据报承载,握手通告端点)/ QUIC(TLS1.3 原生,stream 直映射)。消费面零差别——机制代码不改一行换介质
形态 消息交互模型三选一:数据报(尽力)/ 请求回调(关联应答)/ 流式(可靠有序+背压)
集群标签 ClusterTag 集群归属编号——握手期核对,错集群连接拒绝(拨入方与服务端必须一致)
成员制 ∥ 地址制 寻址两制:成员制 = 成员表路由(较小 NodeId 拨号防对拨);地址制 = 有地址者直连(身份握手得知)
leader ∥ follower raft 角色:leader 接受写入并复制给其他节点;follower 被动跟随——leader 掉线时由存余节点重新选出
多数派 ⌊N/2⌋+1(N=3 即 2 台)——一条写入拿到多数派落盘才算提交;选举同样要多数派投票

3. 运行拓扑与一致性

3.1 节点与集群:谁包含谁

节点是唯一的运行时实体(一个 NodeEndpoint = 传输枢纽 + 装配的机制); 集群不是实体,是三个层面的共同约定

集群 = ① 一致的集群标签(ClusterTag——握手期核对,防串集群)
     + ② 一致的成员表(ClusterConfig——谁是这个集群的成员、谁是 learner)
     + ③ 一致的日志与状态(多数派复制的产物)

   ┌──────────┐          ┌──────────┐          ┌──────────┐
   │  节点 A   │          │  节点 B   │          │  节点 C   │
   │ (leader) │          │(follower)│          │(follower)│
   └────┬─────┘          └────┬─────┘          └────┬─────┘
        │      每对节点一条 TCP 长连接          │
        └──────────────┬───────┴─────────────────────┘
             (复制 / 选举 / 快照 / 反熵全部共用这条链路——
               连接数 = 节点对数,不随协议数量增长)
  • 链路由 NodeId 较小的一方主动拨号并负责重连(另一方只监听——防止双方同时拨导致连接 抖动);断线指数退避自动重连,恢复后链路自动续上。
  • 客户端(地址制)不进成员表:拨入即用,身份握手得知,断开不自动重连。

3.2 启动流程(从 StartAsync 到可写入)

TierRaftNodeBuilder / ClusterBuilder .StartAsync()
  │
  ├─► ① 传输起网:开始监听 + 按成员表向各对端拨号
  │       └─ 每对节点:三步握手(协议版本 / 集群标签 / 安全档核对)
  │          —— 任一环不匹配 = 拒连(fail-fast,绝不带病运行)
  ├─► ② raft 选举
  │       ├─ N=1:多数派 = 自己,直接自举为 leader(单机形态)
  │       └─ N≥2:候选者拉票,拿多数派票者当选 leader
  └─► ③ leader 就绪——接受写入(非 leader 收到写入 = 明确报错,客户端重路由)

全程对使用方只有一个观察点:raft.IsLeader / raft.LeaderId(§6.2)。

3.3 谁和谁通信

通信 谁发起 → 谁接收 干什么
写入复制 leader → 各 follower 你的写入(ReplicateAsync)由 leader 追加日志并复制,多数派落盘即提交
选举投票 全员 ↔ 全员 leader 掉线(心跳超时)时存余节点互拉票选新 leader
心跳 leader → followers 告知"我还活着+我的日志进度";follower 持续收不到 = 发起换届
快照流(单源) leader → 落后节点 follower 日志缺口越过快照边界,增量追不上 → 整段基线流式安装
快照拉取(多源) 落后节点 ← 全体持有者 装配 Swarm 后:块清单 + 并行拉块 + 坏块换源——基线带宽不再全压 leader
反熵比对 leader ↔ 各对端(轮转) 按内容树逐层比对,只传差异块——持续收敛漂移
gossip 扇出 节点 → 活跃视图邻居 通知/通告类消息逐跳转发,最终散布全网(P2P 模式,§8)

客户端只和 leader 通信写入(非 leader 明确报错引导重路由);读可与任意节点交互 (线性读见 §3.4)。

3.4 两种一致性:怎么启动、代价是什么

线性一致(缺省——不用做任何额外配置):装配 raft 即得。写走默认完成档, 读走 ReadIndexAsync

写:await raft.ReplicateAsync(cmd)
    ── 返回时:多数派落盘 + 已应用到状态机(read-your-writes)
读:await raft.ReadIndexAsync(ct)
    ── leader 向多数派确认身份后返回读位点(分区期拿不到多数派 = 超时,不返回过期数据)
代价:提交要多数派在线——半数以上节点不可达时写入不可用(这是强一致的物理代价)

最终一致(按需组合三件套)

手段 怎么启动 语义
LeaderLocal 完成档 单次调用 ReplicateLeaderLocalAsync;或 RaftOptions.WithReplicationAck(ReplicationAck.LeaderLocal) 整体切换 leader 本地落盘即返回(延迟最低)——换届可能丢未复制尾部,接受异步收敛(Redis 类场景)
多源分发 + 反熵 产品节点 WithSwarm(...) + WithAntiEntropyInterval(...)(见 raft-node.md);引擎级组件 SwarmSync/SwarmAntiEntropy(§6.3) 基线内容寻址、多源并行拉取;对账只修差异——持续收敛
gossip 广播 P2P 模式 WithBroadcast()(§8.2) 通知面:最终散布、不保序不保达

怎么选:账本/配置/元数据 → 线性一致(缺省);高吞吐低延迟可容忍换届丢尾部 → LeaderLocal;大基线分发与漂移修复 → Swarm + 反熵;成员事件通知 → gossip。

3.5 节点数怎么定

多数派 = ⌊N/2⌋+1(投票节点数;learner 不计入):

规模 可运行 容错 说明
N=1 0 台 单机形态:启动即自举 leader,本地落盘即提交——开发/单机部署
N=2 0 台 多数派 = 2,任一台离线即不可写不可选——能跑但不建议(没换来容错,白付一致性成本)
N=3 1 台 生产最小推荐
N=5 2 台 跨机架/可用区起步
N=2k+1 k 台 容错上限 = 少数派;偶数不增加容错只增加成本(N=4 与 N=3 容错相同)
+learner 附加观察副本:收日志可读、不投票不计多数派——读扩容/容灾不损写入可用性

运行中变更:Membership 在线加减成员(两步提交);learner 角色在成员表声明。


4. 三形态怎么选

你的需求 关键语义
心跳/探测/通告(丢了没关系) SendDatagramAsync 尽力送达——对端未连/未注册/注入丢弃全部静默
一问一答(RPC) SendRequestAsync CorrId 关联 + 超时;目标未连NetIOException(快速失败,不白等超时窗)
要确认到达 + 可接受重发 请求回调 + RetryPolicy at-least-once——重发经传输端去重窗口缓存重放(不重复执行)
大块/持续传输(快照/备份) OpenStreamAsync 可靠有序 + 背压(写 await 整链传导);GB 级 O(单帧) 内存

4.1 请求回调

// 服务端
public sealed class MyHandler : IRequestHandler
{
    // 同步回调——快进快出(重活自排队);应答经 reply(零次调用 = 对端超时)
    public void OnRequest(NodeId from, ReadOnlyMemory<byte> payload, IReplyContext reply)
        => _ = reply.ReplyAsync(Process(payload));
}
endpoint.RegisterRequestHandler(MyProtocolId, new MyHandler());

4.2 流式会话

// 发起端
await using (var stream = await endpoint.OpenStreamAsync(target, MyProtocolId, ct: ct))
{
    foreach (var chunk in chunks)
        await stream.WriteAsync(chunk, ct);      // await = 背压(对端慢则本端等待)
    await stream.CompleteAsync(ct);              // 正常收尾——对端枚举自然结束
}

// 接受端(OnStream 回调里起消费循环——循环任务由使用方管理,如经 Core TaskSink 受控提交)
public sealed class MyAcceptor : IStreamAcceptor
{
    public void OnStream(NodeId from, IWireStream stream, ReadOnlyMemory<byte> openPayload)
        => ConsumeAsync(stream);             // 你的消费循环入口(openPayload = 开流载荷)
    private async Task ConsumeAsync(IWireStream s)
    {
        await using (s)
            await foreach (var frame in s.ReadAllAsync())
                Process(frame);                        // 逐帧——不要攒全量
    }
}
endpoint.RegisterStreamAcceptor(MyProtocolId, new MyAcceptor());

会话不跨重连恢复——链路断即 Reset,上层重开。


5. 传输配置与调参

装配器方法覆盖常用项;全部运行参数WithTransport 调整,两种形态:

// 整份注入:自带一份配置(显式方法未触达的项以注入值为准)
var options = TransportOptions.Default(null, peers)
    .WithHandshakeTimeout(TimeSpan.FromSeconds(5));
await using var a = await ClusterBuilder.Create(nodeId)
    .WithTransport(options)
    .Listen("0.0.0.0", 9400)                 // 显式方法仍裁决对应项
    .StartAsync(ct);

// 管道改写:在装配结果上叠加(可多次调用,链尾依次执行——最终裁决)
await using var b = await ClusterBuilder.Create(nodeId)
    .Listen("0.0.0.0", 9400)
    .WithTransport(o => o.WithReconnect(
        TimeSpan.FromMilliseconds(100), 2.0, TimeSpan.FromSeconds(1)))
    .WithTransport(o => o.WithKeepalive(true))
    .StartAsync(ct);

裁决顺序:显式方法(Listen/Peers/ClusterTag/WithUdp)> WithTransport 注入/管道 > 缺省表。 NetClientBuilder 同款(客户端拨入带标签集群记得 ClusterTag)。

5.1 常用调参示例

// 重连退避:raft 场景封顶建议 < 选举窗一半——选举节奏不被重连劫持
o => o.WithReconnect(initialDelay: TimeSpan.FromMilliseconds(100), backoffFactor: 2.0, maxDelay: TimeSpan.FromSeconds(1))

// 传输级保活(NAT/防火墙长连接;双方都开才生效)
o => o.WithKeepalive(enable: true, interval: TimeSpan.FromSeconds(10), maxUnanswered: 3)

// 请求回调:高并发在途 / 大响应去重预算
o => o.WithRequest(timeout: TimeSpan.FromSeconds(3), pendingCapacity: 8192,
                   dedupCapacity: 4096, dedupMaxBytes: 32 << 20)

5.2 旋钮速查表

你要调 旋钮(缺省) 什么时候动它
握手超时 HandshakeTimeout(3s) 跨地域高延迟链路放宽
重连节奏 ReconnectInitialDelay(100ms)/ ReconnectBackoffFactor(×2)/ ReconnectMaxDelay(2s) 与选举窗/业务重试节奏对齐
传输保活 EnableKeepalive(关)/ KeepaliveInterval(10s)/ KeepaliveMaxUnanswered(3) NAT/有状态防火墙环境
请求超时 RequestTimeout(3s;per-call RequestOptions 覆盖) 慢接口单独放宽走 per-call
请求在途上限 RequestPendingCapacity(4096,满抛 fail-fast) 高并发 RPC
at-least-once 重放窗口 RequestDedupCapacity(1024)/ RequestDedupMaxBytes(8MiB) 大响应 + 重试语义
流式背压窗口 StreamWindowFrames(16 帧) 大块吞吐 vs 内存占用
写队列深度 WriteQueueFrames(64 帧) 写突发削峰
UDP 单报预算 UdpMaxDatagramBytes(1200B) 适配链路 MTU
慢回调观测阈值 SlowDispatchThreshold(10ms) 调低抓慢 handler

5.3 UDP 数据报承载

builder.WithUdp(9401);        // 端点绑定 + 握手通告对端;port 0 = 动态端口
  • 数据报/请求回调可走 UDP 承载(尽力语义不变);单报预算缺省 1200B(不做 IP 分片 依赖)——超预算自动回落 TCP 承载,调用方无感。
  • 加密场景:KeyPair 档 UDP 报文带认证标签;mTLS 档 UDP 回落 TCP(见 §10)。

5.4 生效配置观测

endpoint.Options        // 装配自证——全部裁决后的最终值(TransportOptions)

排查"配置没生效"先看这里(管道链尾执行结果即它)。


6. raft 共识

RaftStateMachine(共识引擎)+ ApplyPipeline(提交→应用管道)+ 你的 IRaftStore(持久化)

  • IStateMachine(业务状态机)成套装配:
var store = new MyRaftStore();               // 实现 IRaftStore(持久化归你——内存/文件/任意)
await store.InitializeAsync(ct);
var machine = new MyStateMachine();          // 实现 IStateMachine.ApplyAsync
var pipeline = new ApplyPipeline(store, machine, ApplyPipelineOptions.Default);
var raft = new RaftStateMachine(nodeId, store, transport, pipeline, RaftOptions.Default);
pipeline.SetConfigCallback(raft.PostConfigChanged);
pipeline.StartAsync();
await raft.StartAsync(new ClusterConfig(members));

引擎直拼是自由组合面(持久化归你)。要完整产品节点(TierWAL 存储 + 快照压缩 调度 + 多源反熵 + 传输组装收口),用产品装配器一步到位: TierRaftNodeBuilder——嵌入式同进程与 TCP 集群两种传输形态,本节引擎语义在其上原样适用。

6.1 复制(完成档三选一)

// 默认档:返回 = committed 且 applied(read-your-writes——拿到返回值即可从业务存储读到效果)
long index = await raft.ReplicateAsync(command, ct);

// committed 档:多数派提交即返(不等 apply——吞吐优先;apply 可能滞后,读效果不保证)
long index2 = await raft.ReplicateCommittedAsync(command, ct);

// LeaderLocal 档:leader 本地持久化即返(不等多数派——异步一致,延迟最低)
// ★ 返回的 index 不保证最终在多数派日志中(leader 宕机丢未复制尾部写)
long index3 = await raft.ReplicateLeaderLocalAsync(command, ct);

// 批量:一批一次追加,批尾 applied
long lastIndex = await raft.ReplicateBatchAsync(commands, ct);
  • 非 Leader 立即抛 NotLeaderException——重路由新 Leader 重试:
for (; ; )
{
    var leader = FindLeader();               // raft.LeaderId / 集群发现
    try { return await leader.ReplicateAsync(cmd, ct); }
    catch (NotLeaderException) { await Task.Delay(25, ct); }
}
  • 换届在途等待者以 NotLeaderException 取消——重试即可,不是错误。

怎么选档:读自己的写 → 默认档;吞吐优先且接受 apply 滞后 → committed 档; 高吞吐低延迟且业务可容忍换届丢尾部(Redis 类异步一致形态)→ LeaderLocal 档。 三档是 per-call 语义(同一集群不同调用方各选各档);RaftOptions.WithReplicationAck(ReplicationAck.LeaderLocal) 可把默认入口整体切到 LeaderLocal(部署态弱档开关——committed/applied 档调用不受影响)。 强一致读配合 ReadIndexAsync(多数派身份确认后返回 commitIndex)。

6.2 读与观测

long readIndex = await raft.ReadIndexAsync(ct);   // 线性读(多数派身份确认后;N=1 立即返回)
long commit = raft.CommitIndex;                   // 跨线程观测
bool isLeader = raft.IsLeader;
bool isVoter = raft.IsVoter;                      // false = learner(引导/观察副本的就绪信号)
raft.LeaderChanged += became => { /* 换届通知 */ };
CancellationToken leadership = raft.LeadershipToken;   // 换届感知令牌——丢失 leadership 即取消
  • 租约读(opt-in)RaftOptions.WithLeaseReads(时钟漂移界)——多数派确认后 (选举窗下界 − 漂移界) 窗口内的线性读零往返返回,窗外自动回落心跳确认。 时钟同步纪律(NTP)由运维承担;要求不满足则保持缺省关。

6.3 主动让位与成员变更

  • await raft.ResignAsync()——主动让位(运维:节点下线/维护前先让位):leader 立即 降级,集群经选举窗立刻重选,不等心跳超时窗;非 leader 调用抛 NotLeaderException
  • Membership——单成员变更提案(两步提交,在线加减成员)。
  • follower 落后超过快照覆盖点时自动走快照安装(单源流式 ∥ 多源 swarm——块清单 + K 并行 + 换源重试,使用方无感)。
  • 多源成立:安装完成的节点自动向 leader 上报持有(SourceAnnounce),leader 端持有表 按代累积(快照换代自动清表、换届自动重报)——后续冷节点安装时 holders 含全体持有者, 基线带宽分散到多源,不再全压 leader。SwarmOptions.WithServeBlocks(false) = 纯消费者 (不服务块、不上报);WithAnnounceOnStart(false) = 退出广播面(静态 holders 仍可指名拉取)。
  • 反熵对账(漂移收敛):SwarmAntiEntropy.RunOnceAsync 按内容与对端逐层比对 (Merkle 根相等 = 零传输;不等 = 只传差异块修复)——把分发升级为持续收敛。 周期与对端轮转由使用方驱动(SwarmOptions.WithAntiEntropyInterval 供配置承载; Majority 档无需开启,LeaderLocal/Swarm-only 档建议开启)。
  • learner 副本ClusterMember 角色位 ClusterMemberRole.Learner——日志照常复制/追平可读, 但不参与选举投票、不计入多数派(读扩容/容灾副本不占写入可用性)。

6.3.1 raft 的存储需求面

IRaftStore 是 raft 对持久化的全部需求(term/votedFor/日志追加/截尾/双水位/快照点)—— 实现它即可接入任意存储。fsync 时机由协议层控制(WaitForPersistedAsync 同步点)。

6.3.2 Standby 引导加入(新节点上线)

新节点以 learner 身份启动并向集群宣告,追平自动晋级 voter——追赶期不占多数派席位, 不扰动既有写入可用性:

// 产品装配形态(TierRaftNodeBuilder)——JoinAsync 已随启动编排
var node = await TierRaftNodeBuilder.Create(id, fs, config, machine)
    .WithTransport(transport)        // 或 WithClusterTransport(listen, peers)
    .WithJoin(idA, idB)              // 引导同伴(至少一个当前集群成员)
    .StartAsync(ct);
// 就绪信号:node.Raft.IsVoter 翻真

// 引擎形态:raft.JoinAsync(bootstrapPeers 或 bootstrapEndpoints, timeout, ct)

观察副本(只收日志可读、永不晋级)走引擎面 Membership.AddLearnerAsync(member)


7. 身份与机制挂载

  • IIdentitySource(端口——本层零 IO):跨重启稳定身份。ClusterBuilder.Create(source) 启动时解析;持久化形态(如 TierFsIdentityStore)在组装层实现端口。
  • INodeMechanism(机制挂载端口——零特权):任何协议机制与内建机制同一挂载路径:
await ClusterBuilder.Create(nodeId)
    .WithMechanism(new MyFsmMechanism())     // 你的状态机复制机制
    .WithP2P(seed: bootstrapNode)            // 内建 HyParView(对等发现——P2P 扩展方法)
    .StartAsync(ct);

机制生命周期随 NodeEndpoint 释放(endpoint.Mechanisms 可枚举已挂载件)。

7.1 QUIC 介质(TLS1.3 原生)

var transport = new QuicTransport(nodeId, TransportOptions.Default(
    new IPEndPoint(IPAddress.Loopback, 4433), peers));   // MsQuic 不可用 = 构造 fail-fast
transport.Start();
  • 三形态映射:请求回调 = 短命双向 stream(stream 即关联);流式会话 = 双向长命 stream (QUIC 流控 = 背压天然);数据报 = 单向短命 stream(send-and-forget——.NET 8 无 QUIC datagram API,语义为"单流失败即丢",非 UDP 级无序尽力)。
  • TLS1.3 原生:运行时自签证书(内网模式——加密+完整性免费);身份经应用层握手交换 (ClusterTag 校验错集群 fail-fast);SAN nid 绑定随 mTLS 体系集成。
  • 运行依赖:MsQuic(Windows 11/Server 2022+ 内置;Linux 须 libmsquic)—— QuicCertificates.IsSupported 装配前探测。
  • at-least-once(RequestOptions.Retry):CorrId 贯穿 + 应答端去重窗口缓存重放。

8. P2P 会员与 gossip 广播

无中心服务器的对等组网:HyParView 会员管理维护"我认识谁"(视图),gossip 广播 负责"通知所有人"(最终一致送达)。两者独立启用——只要会员、或只要广播皆可。

8.1 会员管理(HyParView)

await using var endpoint = await ClusterBuilder.Create(nodeId)
    .WithP2P(seed: bootstrapNode)            // null = 本节点是种子(只启动不加入)
    .StartAsync(ct);

// 观测面
var p2p = endpoint.Mechanisms.OfType<PeerMechanism>().Single();
foreach (var peer in p2p.ActiveView) { }     // 对称活跃邻居(有界 k)
p2p.ActiveCount;
概念 行为
活跃视图 在线对称邻居(双向确认),大小有界(缺省 ≤ 4)——消息扇出/转发的目标域
被动视图 备用池(缺省 ≤ 20)——活跃邻居断连时自动补位
JOIN 新节点从种子出发随机游走入组(应答丢失不阻塞——视图收敛交给周期维护)
故障检测 φ accrual 基于心跳到达间隔判定(缺省阈值 8——误判率可忽略),限时检出

常用参数(PeerOptions With 链):WithActiveViewSize(k)WithPassiveViewSize(n)WithHeartbeatIntervalWithPhiThresholdWithJoinWaitTimeout——缺省值适合小中型 对等组起步。

8.2 gossip 广播(最终一致性)

一条消息最终送达全体可达成员——不保证顺序、不保证送达。适合通知/通告/拓扑事件; 要可靠或要确认,走 raft(§6)或请求回调(§4.1)。

// 挂载时开启(缺省关)
await using var endpoint = await ClusterBuilder.Create(nodeId)
    .WithP2P(seed, PeerOptions.Default.WithBroadcast())
    .StartAsync(ct);

var broadcast = endpoint.Mechanisms.OfType<PeerMechanism>().Single().Broadcast!;

broadcast.MessageReceived += (origin, data) => { };   // 最初广播者 + 载荷(同消息至多投递一次)
await broadcast.BroadcastAsync(payload);               // 沿活跃视图扇出,逐跳转发
语义 行为
去重 同一消息(MsgId)全网至多投递一次——多路径副本/转发环回自动归一
TTL 缺省 4 跳,每转发一跳减一,减尽即停(断链拓扑下终止转发)
扇出 沿活跃视图转发,上限可调(WithFanout——缺省整个活跃视图)
载荷上限 缺省 60KB,超限源头即抛(数据报尽力语义,不做分片)

参数(BroadcastOptions With 链):WithTtlWithFanoutWithDedupCapacityWithMaxPayloadBytes


9. 可观测与故障注入

// 故障注入(常设面——InProcess 与 TCP 语义等价,对抗场景双介质复跑)
endpoint.Faults.SetLatency(a, b, TimeSpan.FromMilliseconds(5));
endpoint.Faults.Partition(new[] { a }, new[] { b });   // 双向断
endpoint.Faults.Drop(a, b, 0.1);                       // 10% 丢包
endpoint.Faults.Reset();

// 帧级指标:Core ObservabilityHub 的 Net 视图(hub.Net——帧收发采样/CRC 失败/握手失败/
// 数据报丢弃,per-protocol tag);Builder 经 WithObservability(hub) 接入

10. 安全档(加密与认证)

何时用 装配
明文(缺省) 可信域内网 零配置
KeyPair(推荐) 无证书体系的内生集群(WireGuard 式) .WithKeyPair(SecurityOptions.KeyPairPinned(ownKey, new PinnedTrustStore(peers, peerKeys)))
mTLS CA/合规场景(短期证书+轮换) .WithMutualTls(cert, caCollection)(证书 SAN 含 nid:<hex32> 条目)
// KeyPair 档:每节点静态密钥对 + 对端公钥钉扎表(信任强度 = mTLS)
using var own = NodeKeyPair.Generate();   // 持久化:ExportPrivateKey → 安全存储 → FromPrivateKey
var security = SecurityOptions.KeyPairPinned(own,
    new PinnedTrustStore([peerA, peerB], [peerAKey.PublicKey.ToArray(), peerBKey.PublicKey.ToArray()]));
await using var endpoint = await ClusterBuilder.Create(nodeId)
    .WithKeyPair(security)                // 此后全帧 AEAD 加密+认证(MAC-only 粒度可选)
    /* ... */
    .StartAsync(ct);
  • 防降级 fail-closed:本端安全档与对端不匹配立即断连——永不静默降级明文(含 KeyPair↔mTLS 互遇)。
  • TOFU 形态SecurityOptions.KeyPairTofu(own, trust) 首连学习对端公钥(SSH known_hosts 式——首连有中间人窗口,半可信内网用);持久化实现 ITrustAnchorStore 归组装层。
  • UDP 承载:KeyPair 档数据报带认证标签(会话派生——篡改丢弃);mTLS 档 UDP 回落 TCP。

11. 反模式

  • 绕三形态直造 socket——丢掉同构验收/超时关联/背压/故障注入/帧协议,全部重造必漂移。 一切收发经 IProtocolTransport
  • raft IStateMachine.ApplyAsynccommand 跨调用持有——仅调用期间有效(存储读取 视图),保留必须 ToArray()。(传输 handler 载荷相反——每帧独立缓冲可持有。)
  • catch NetIOException 当静默成功——它是"不可达快速失败",该立即重路由/上报; 静默吞 = 白等超时窗。
  • 协议号用注册区外——使用方一律 0x60-0xAF;核心区号经 internal 挂载口(公开口放行即抛)。
  • 流式读侧攒全量——ReadAllAsync 逐帧交付是契约(GB 级 O(单帧)),消费方攒 List 违约。
  • _ = SendRequestAsync(...) 丢弃任务——异常静默吞 + 无超时治理;await 它或交 TaskSink 受控提交。
  • gossip 广播当可靠通道——最终一致语义(不保序不保达,TTL 到期即弃);要可靠确认 走 raft 复制(§6.1)或请求回调(§4.1)。
  • 偶数节点当容错——N=2/N=4 的容错与 N=1/N=3 相同(多数派 = ⌊N/2⌋+1,见 §3.5); 偶数只是多付一台成本。

12. 想深入