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)、
WithHeartbeatInterval、WithPhiThreshold、WithJoinWaitTimeout——缺省值适合小中型
对等组起步。
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 链):WithTtl、WithFanout、WithDedupCapacity、
WithMaxPayloadBytes。
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.ApplyAsync的command跨调用持有——仅调用期间有效(存储读取 视图),保留必须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. 想深入
- 积木拼装全景与决策树:
../COORDINATION.md - 性能基线(形态面 loopback RTT/吞吐、raft 引擎三节点吞吐):
perf/loopback-baseline.md - raft 完整产品节点(TierWAL 存储 + 快照压缩 + 多源反熵):
../../TC.Tier.Products.Net/docs/raft-node.md - Core 层积木(TaskSink/日志/可观测):
../../TC.Tier.Core/COORDINATION.md - 线格式生成器(WireMessage/BinaryLayout):
TC.Tier.CodeGen