笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 接入与通信

    • 游戏基础架构与工具
    • 接入网关(长连接接入层)
    • 消息总线(共享内存 IPC)
    • 帧同步(Lockstep)
    • 全栈极限低延迟(物理层→应用层)
    • KCP 与 QUIC
  • 网络与服务网格

    • CNI 与 K8s 网络插件
    • 容器运行时:Docker 与 containerd
    • Istio 与 Cilium 服务网格
    • 服务网格:中心化 vs 去中心化
    • eBPF 原理与在网络/可观测/安全的落地
    • 自研 Mesh 服务网格 × K8s 部署
    • 自研 Mesh × K8s · 数据面深水区
    • Tco 协程框架 · 运行时深水区
    • K8s 异构 & 网络插件
  • 有状态服务

    • 分布式游戏存储(分布式 KV)
    • 有状态服务的数据迁移
    • 有状态服务的数据恢复与容灾
    • 有状态服务的 K8s 编排层治理(防漂移 + 节点崩溃恢复)
    • 内存配置热刷新与更新机制
  • 算法与协程

    • 一致性哈希四算法实现(Ring Hash / Ketama / Maglev / Jump Hash)
    • 蓄水池抽样(Reservoir Sampling)与加权扩展
    • C++ 协程:有栈 vs 无栈 vs C++20 原生(函数染色与 libco)
    • Raft 与 Gossip:CP 强一致共识 vs AP 最终一致传播
  • 流量与承载

    • 游戏秒杀场景承载
    • 令牌桶与漏桶
    • 限流与熔断
    • 限流:互联网 vs 游戏
    • 灰度发布 · Canary / BlueGreen / A-B / Shadow
    • 服务器日常系统开发与维护 SOP
    • 极限发压工具设计(多长连接 + 发压任务)
    • 性能分析与优化方法论(六条排查线)
  • 编译与排查

    • 编译优化与 LLVM/Clang/GCC
    • Sanitizer 工具链与内存泄漏定位

self-mesh-k8s-deepdive — 闪卡

8 段路径 · socket 三档旋钮 · C 单线程无锁 · tbus 点分位分发(细节 → self-mesh-k8s-deepdive.md)

记忆口诀

8 段路径:2 段 tbus SHM / 2 段跨机 TCP / 2 段本机 TCP / 2 段 tmesh 内部
socket 三档:2MB buf / TCP_NODELAY / stamp 攒批合包 / host_cache 免二次查
C 高性能:单线程无锁 / 内存池 / 位运算 / Jump Hash / 数字 ID 是前提
tbus 分发:点分位三层查表 O(1) / 5 类策略 / TTL=4 防意外反射
压测疤痕:CPU 54%→33% / 迁移周期反复 / turbulance 事故 / 9s 防抖 / CVM 方案二 / tbus 判空重试 / 60s 延迟销毁
压测疤痕:CPU 54%→33% / 迁移周期反复 / turbulance 事故 / 9s 防抖 / CVM 方案二 / tbus 判空重试 / 60s 延迟销毁

Card 1 · 流量路径 8 段解剖

Q:一条业务包从 A 机业务进程到 B 机业务进程一共走了几段?哪些段是共享内存、哪些段是 TCP?系统调用密度分别是多少?

A:8 段——① 业务A→msc-A tbus SHM(0 syscall),② msc-A→tmesh-A TCP(本机 hostIP),③ tmesh-A 内部路由分派(0 syscall,就地改 header),④ tmesh-A→tmesh-B 跨机 TCP(epoll_wait 在 nss_run;发送侧 stamp 攒批 N 包合并成 1 次 write),⑤ tmesh-B 收包(读 tip->host_cache 免二次 hash_find),⑥ tmesh-B→msc-B TCP(对称),⑦ msc-B→业务B tbus SHM(msc_tbus_send),⑧ 回包对称 8 段。就近路由 get_nearest_instance_index 命中时把跨机的 ③④⑤⑥ 短路成本机闭环(本机内 4 段)。

代码坐标:msc_tbus.cpp → msc_tbus_send + msc_tbus_run · msc_net.cpp → cn_write + msc_send_data · tmesh_main.c → tmesh_send · tmesh_net.c → _tmesh_send_msg + _tmesh_random + _tmesh_return_tgw。

Card 2 · socket 三档旋钮

Q:socket 层优化按"离系统调用远近"分三档,各自砍了什么开销?收益最大的一档是哪档?

A:

  • 档 1 · 内核旋钮:SO_RCVBUF/SO_SNDBUF=2MB(一次 write 不 EAGAIN)+ TCP_NODELAY=1(免 Nagle 40ms)+ SO_KEEPALIVE(半死连接探测)。为什么关 Nagle 后还要应用层攒批:Nagle 是"tcp 时序合并",应用层攒批是"业务时序合并"(tick 内),两者互不冲突、组合使用才能把 syscall/包比压到接近 0。
  • 档 2 · 应用层缓冲:stamp_create(32K×16K) 内存池 + stamp_get 拿块 + slist_insert_tail 挂到连接 → ncn_add_temp 就绪队列(发送不立即 write)→ 主循环末尾 tmesh_send 遍历 nss_get_temp 批量刷 ncn_stamp_write —— N 个逻辑包合并成 1 次 write。
  • 档 3 · 免查缓存 · 最大收益:svr_heartbeat 里 tip->host_cache = mn->cache 记录来源连接;_tmesh_send_msg 转发时直接 cache_dest = tip->host_cache 跳过 hash_find。作者留了实证注释:cmpTBusPair 13.22% + hash_find 10.39% CPU,优化后 CPU 54%→33%。

保护:slist_nums(&cache_dest->list_data) >= 32000 整段丢弃并 gsm_alert,慢连接不拖爆内存。hostNetwork ≠ UDS:本机 msc↔tmesh 是 TCP + hostIP 环回,不是 UDS——因为 msc 要在多个 tmesh 之间动态切主备。

Card 3 · C 高性能 5 条推理链

Q:为什么用 C 而不是 Go/Rust 重写?"C 更快"这句话展开来是哪 5 条推理?

A:5 条推理链的叠加——

  1. 单线程无锁主循环 vs Go goroutine + mutex 的调度开销:tmesh_start 串行跑完"检查 → 定时器 → 发送 → epoll → 再发送",核心数据结构全部单线程独占,无 mutex / atomic / lock-free 复杂性。
  2. 手写 stamp 内存池 vs GC 抖动:热路径 0 分配、0 释放,稳定 P99(Go GC 5~20ms STW 是不可接受的)。
  3. 位运算路由 vs Go int/interface 拆包:busid & 0xff、(busid >> 8) & 0xff、(a ^ b) & 0x01000000、htonl(busid & 0xffff0000) 全是单周期指令。
  4. Jump Consistent Hash · O(log n) · 0 内存:64bit LCG 常量 2862933555777941757ULL(还有 5 个可选素数),j = (b+1)*(1<<31)/((*key>>33)+1) 算下一跳变点。实测 100 桶 10M/s、1000 桶 7.2M/s。对照 Ketama 建环 + 虚节点 + 字符串 hash,同 100 桶只能几十万 op/s。
  5. 数字 TBUSID 32bit 是整套算法的前提——字符串 ID 会让前 4 条全部作废。

CVM 130% CPU 打满万兆网卡口径:整机总 CPU(多核合计),依赖 SDK 直连多通道 + 多进程 tmesh 并发,不是"1.3 核吃满万兆"。单核单线程无锁是"每个 tmesh 实例内部"的写法。

Card 4 · tbus 点分位分发

Q:tbus 的 32bit TBUSID 如何映射到具体实例?三层查表各是什么复杂度?5 类路由策略分别用什么算法?

A:点分位 → 三层查表 → 5 类策略:

  • 点分位编码:TBUSID 32bit ≡ zone.route.svrgroup.instance(例 0.1.0.0)。zoneid = busid & 0xff、route_id = (busid >> 8) & 0xff、idx = htonl(busid & 0xffff0000)。
  • 三层查表:route_table[zoneid] → route[dest_routeid] → uinMod[idx] → dl_next(list) → STBusIP——全部数组下标 + 位运算 O(1),无 hash 无锁。
  • 5 类策略在 route 内选实例:
    • MOD / MOD_BACKUP → _tmesh_mod:id = qqnum % route->total,同桶自动 backup。
    • CO_HASH → _tmesh_co_hash:Jump Hash + active_list 二次 rehash。关键坑:二次 rehash 不能把 key 重置为 qqnum(会让故障 mod 上的 key 又落回原桶,剩余机器雪崩)——必须用 LCG 已 mutate 的 key。
    • RANDOM → _tmesh_random:就近优先 → rand() % route->total。
    • MASTER_SLAVE / MOD_MS → 首个成功打 ECONN_MASTER,其余 ECONN_SLAVE。

共享内存底座:tbus_init(iBusKey, 20480000) 20MB SHM 环形队列,生产者/消费者各原子写 head/tail 无锁。TBUS_ERR_CHANNEL_FULL 时若 dwHead == dwTail == 0 判空重试一次(msc_tbus_send)——共享内存无锁队列的经典坑。

消费循环:msc_tbus_run(sc, 300) 一次 tick 上限 300 条;peek + process + delete 三段式让处理失败可以不出队。

TTL 防环:header->ttl 0→4 初始化、1 丢弃、否则递减。这不是多跳追踪——全连接单跳广播的语义里包根本不该跳超过一次,TTL 只是"防意外反射"的最后防线。

Card 5 · 压测与演进疤痕(用 C 到底比其他语言多拿到了什么)

Q:用 C 写和其他语言写的差距到底是什么?压测里发现了什么、修了什么、留下了哪些疤痕?

A:不是"C 更快",是"C 允许把每个决策都拍在代码里"。

三层地板:① syscall/包 的地板(Envoy 3× socket 开销 vs ncn_add_temp 批量刷 syscall/包 ≈0)② P99 抖动的地板(Go GC write barrier +10~20% CPU vs stamp 预分配 0 分配)③ 单周期指令 vs 拆箱开销(C busid & 0xff 单周期 vs Go interface{} 拆箱 20~50 周期)——Rust 能打平前两层,Go/Java 天生打不平。

7 大疤痕:

  • CPU 54%→33%:_tmesh_send_msg 里 perf 抓到 cmpTBusPair 13.22% + hash_find 10.39% = 23% CPU 集中在 tbus pair 统计。决策:整段注释掉统计 + 引入 tip->host_cache 免二次 hash_find。保留原代码为注释而非 git rm——决策日志留在源码。
  • 迁移周期条件三次反复:14s(防迁移太快)→ 7s(缩短玩家延迟感知)→ 2023.11.23 去掉(怀疑影响正确切换)→ 2023.12.07 恢复(panxie 反馈网页监控页显示"幽灵实例")。产品面观察窗口 vs 数据面正确性的拉锯。
  • turbulance 双层启发式:起因是"全军"(赛事场景)时起了 2 个同 BusId 实例导致来回踢;修复"短时间 IP 端口切了 2 次" + 二次修补 ipChangeFromDisable 白名单(正常重启不误判)。
  • 9 秒防抖:update_master_connection 里 msc 侧切换间隔 = mesh 侧 HeartBeatTimeOut = 9000ms——不是防抖,是多方超时对齐。
  • CVM 多通道选方案二:virtio 打折 40~45%,方案一(tmesh 本地多通道)新增 2 次中转;方案二(SDK 直连多通道)无多余中转,不破坏单线程无锁架构,Jump Hash 靠数字 ID 直接可用。
  • tbus 共享内存判空重试:TBUS_ERR_CHANNEL_FULL 时双检查 dwHead == dwTail == 0——共享内存无锁队列的经典瞬态处理(msc_tbus_send)。Go/Rust 用 channel 自动保证内存序,代价是有 atomic 开销。
  • 60s 延迟销毁:tmesh_destroy 只标记时间戳、__tmr_tmesh_clean 60s 后才真删——重连能继承缓冲区,配合 iQueueSize=32000 discard 保护避免堆积。

结论:Go/Rust 项目里这些决策要么被抽象进接口/配置管理层丢失上下文,要么被 GC/runtime 遮蔽在语言运行时里根本看不到。代码本身就是决策日志——这条价值在 GC 语言里几乎拿不到。

(细节 → 见 self-mesh-k8s-deepdive.md#主干五-压测与演进疤痕-用-c-到底比其他语言多拿到了什么)

最近更新: 2026/9/10 11:38