跳转至

zcache:从零造一个内存缓存服务器

这一章讲什么: 造一个像 Redis 那样的内存缓存,但每个零件都自己手搓: 线协议(ZEMP,二进制帧)、解析状态机、LRU 存储引擎(哈希表 + 双向链表)、线程服务器,外加三个客户端。 书明说了教学实现与生产的差距在哪:Redis/memcached/KeyDB 在同地基上加的是分片锁、优雅停机、TLS、 认证、持久化、复制1。

主线例子:两个。① LRU 驱逐顺序测试:A、B、C 进容量 3 的缓存,GET A 把它提拔,SET D 挤掉谁? ② 一张 ZEMP 帧的字节布局,从 magic 到 payload 一字节一字节摊开。

1. 顶层全景:三层结构,三件工具

  线协议层   protocol.zig —— 只序列化/反序列化字节,不知道网络
     │      ZEMP 帧: [magic 0x5A][version][type][flags][payload 长度 u32 大端][payload]
  存储引擎层 cache.zig —— 只懂数据结构,不知道网络
     │      StringHashMap(*Node) + 侵入式双向链表 + 一把互斥锁;get/set/delete/count
  网络层     server.zig —— thread-per-connection,每个连接一个阻塞 I/O 循环

  三件工具:  zemp_client(固定测试序列,验正确性)
             zcache_cli(一次调用一条命令,Unix 哲学)
             bench(ReleaseFast 编译,量吞吐与延迟)

  图说:三个源文件各管一层,互不越界;main.zig 薄到只解析参数、接线、调用 server.run()。[^2]

build.zig 定义五个目标(run/client/cli/bench/test),只有 bench 用 ReleaseFast 编译——不让基准工具自己的开销污染测量3。

2. 线协议 ZEMP:固定宽度头,拒绝扫描分隔符

ZEMP(Z cache Exchange Message Protocol)的设计动机:文本协议的解析开销和脆弱性——payload 里混个换行或空字节就能弄坏简单文本协议。固定宽度二进制头让解析器不扫描任何分隔符就能确定一条消息的确切字节数4。

帧的结构,8 字节定长前缀 + 变长 payload5:

字节 0     magic   = 0x5A        ← 错位读流的粗筛子
字节 1     version             ← 两端结构假设一致
字节 2     type                ← 请求里是命令号,响应里是状态码
字节 3     flags   = 0x00      ← 保留,必须为 0
字节 4–7   payload 长度,u32,大端
其后       payload(命令相关:GET/DEL 是 u16 前缀的 key;SET 是 key + u32 前缀的 value + u64 TTL)

图说:头用 packed struct(u32) 声明——线上布局和内存布局完全相同,编译器对结构尺寸和字段偏移的推理没有任何隐藏填充6。长度字段大端:不同硬件架构的机器对长度字段的解释逐字节一致,不用手工移位7。两个安全闸门在 parsePrefix():magic 不对、版本不对、声明长度超过 64 MiB 的 MAX_PAYLOAD_LEN——没有这一闸,恶意客户端发个小头声称几 GB payload,服务器就会在缓冲分配时崩掉8。

帧结构是静态规格,状态机是在时间上把字节拼成帧的动态过程——两件事被刻意分开9。

3. 状态机:调用栈替你当状态

连接从「读前缀」状态开始:readSliceAll() 阻塞线程直到恰好 8 字节到齐;头解析出 payload 长度后,转换到「读 payload」状态;payload 读完、分发、响应成功,回到「读前缀」,准备同连接上的下一帧10。出错的 parseRequest 在回了错误响应后也回「读前缀」——连接继续活着;CLOSED 终态从两个状态都能到达:EndOfStream(干净或半帧断连),以及读前缀时发现 bad magic / 版本不支持 / 超长 payload——后者回完错误立即关连接11。

最有 Zig 味的一笔:这个状态机根本没有显式实现——没有 State 枚举、没有连接结构上的状态字段、没有转换表。线程调用栈和程序计数器替它值班:readSliceAll() 阻塞在「还差字节」的那一点,字节到了就从那一点原样续跑;两段式读取(先 8 字节定长头,再变长体)就是两个顺序阻塞调用,阻塞 I/O 让调用栈免费编码了解析位置,零记账12。事件驱动实现享受不到这个性质——这正是上一章异步代理需要显式状态追踪每连接读到哪里的原因13。

部分读与缓冲碎片化是这套架构的核心考题:TCP 不保证数据按发送时的块到达。readSliceAll() 读到半帧就在线程上等,剩余字节到了从断点继续——调用之间不需要保存任何状态,因为线程自己保住了执行上下文;反方向,一个连接背靠背塞来多帧,连接线程逐帧顺序处理完再发下一次读,帧与帧之间不丢不跳14。

一个容易混的细节:MAX_PAYLOAD_LEN(64 MiB)和每连接的 read_buf_size(几千字节)是两个独立设置,回答两个不同问题——前者是协议级策略(客户端单请求允许的最大值),后者是 I/O 级机制(从套接字拉字节的暂存缓冲)。把每个连接的常备缓冲开到协议允许的最大值,等于为了让一种罕见的请求形状更快,浪费每个空闲连接几 MB;正确姿势是小常备缓冲处理常见情况,那条随请求尺寸分配、用完即 defer free 的精确大小缓冲,只在该请求的生命期里存在15。

4. 存储引擎:为什么非要两个数据结构

高性能缓存的效率由两件截然不同的事共同决定,而且都得 O(1):按 key 取数据和维护元数据(谁最该被驱逐)16。

哈希表天然干第一件事:key 到值的平均 O(1) 查找;没有它,数据集一大,线性搜索会慢到灾难17。但哈希表是无序的——它没有任何机制记录条目的「年龄」或「 relevance」,而驱逐策略(LRU)恰恰需要知道该扔谁18。第二个数据结构叠上去:一条双向链表提供顺序,哈希表的每个条目带指向链表邻节点的指针。条目被访问或插入时挪到链表「头」(最近使用);尾巴上的就是 LRU,满了第一个被驱逐;维护这些指针,重排是 O(1)19。

这套冗余是内存换时间的交易:每条目存两份指针抬高单条内存开销,但消灭了周期性扫描或排序——那些会引入延迟尖峰20。Zig 还能再进一步:侵入式链表——不为链表单独分配包装节点,而是把 next/prev 指针直接嵌进数据记录本身;CPU 取一条缓存条目看值的时候,重排所需的元数据跟着同一个缓存行一起到,零额外成本21。

5. 主线例子①:LRU 驱逐的顺序账

书里最教学相长的一个单测,把上面全部理论变成可对账的操作序列22:

容量 3 的缓存:
set A → set B → set C      访问序(MRU→LRU): C · B · A
get A                      提拔 A:        A · C · B
set D(满了,先驱逐尾巴)     驱逐 B:        D · A · C

断言: get B == null(被驱逐)
      get A / C / D 都命中
      stats.evictions == 1

图说:每一步都只动四个指针(detach 两处 + attachHead 两处),没有任何遍历;驱逐候选永远是尾巴,无需扫描数据集23。

两个链表原语撑起全部操作,不变式 therefore 可审计:head.prev 恒 null、tail.next 恒 null、每个节点 N.prev.next == N 且 N.next.prev == N24。

  • detach(node):把节点从当前位置摘出——有前驱则前驱的 next 跨过它,没有则它是头、后继继位;右侧对称;最后节点自身两指针清零,漂浮待位。全程序只有这一处摘除节点25。
  • attachHead(node):漂浮节点插到 MRU 端——next 指向现任头、prev 清零、旧头的 prev 回指、安装新头;尾巴为 null 时(空表),新节点身兼头尾26。

get() / set() / 驱逐,全部由这两个原语组合而成27。

6. get / set / delete:并发安全的三个面

公共 API 的统一姿势:进函数先 mutex.lock(io),defer 解锁——命中、未中、惰性过期哪条路退出,锁都照放,无手工清理样板28。

get() 的四步:哈希表查到节点,未中记一次 miss 返回 null;惰性 TTL 检查——到期就当场 removeNode、记一次过期、当未中返回,没有后台线程,「过期发生在访问的那一刻」29;命中则 detach + attachHead 提拔到 MRU;最后一步在仍持锁时把值复制一份——get() 绝不返回指向节点内存的指针:锁一释放,别的线程可能 set/delete 同 key 把底层字节释放,任何别名切片都会悬空。复制完再解锁,调用方拿到的值才能活过下一次变更30。

set() 的三条路:key 已存在 → 原地更新——释放旧值、复制新值、key 和节点结构都不重分配,然后照样提拔(「更新算一次使用」)31;缓存满 → 急迫驱逐——尾巴就是 LRU,先 removeNode 再分配,内存占用永不越界32;新 key → 三次分配(节点、dupe key、dupe value),每处分配紧跟一个 errdefer——中途 OOM,已分配的 key 和节点被正确回收,缓存保持一致不泄漏33。

delete() 的幂等:map.get(key) orelse return false——对不存在的 key 删两次不是错误,是定义良好的正常结果;找到就 removeNode 三连(出映射、出链表、释放),返回 true。返回 bool 而非 error 是刻意设计:CLI 里对缺失 key 的 DEL 回 NOT_FOUND,就来自这个 false34。

removeNode 的顺序有讲究:必须先从映射和链表双双摘除,再释放内存——否则映射或链表会握着指向已释放内存的悬空指针35。freeNode 三连 free(key、value、节点)用同一个分配器,全程序唯一的释放点;「调用前必须已摘除」的契约写进注释,removeNode 是唯一强制它的地方,外加 deinit 整体拆除36。

还有一个容易被漏掉的内存细节:映射里存的 key 不是调用方的切片,而是指向 node.key(节点自有的堆上副本)的切片——映射键的生命期与节点绑死,调用方切片出作用域后永不悬空37。外加初始化时用 ensureTotalCapacity 预分配,避免热缓存预热阶段 rehash 带来的不可预期延迟尖峰38。

7. 网络层:thread-per-connection 的得与失

server.zig 的 accept 循环(与第 7 章并发 echo 同构):accept 出错记日志 continue,不拖死全服;每连接堆分配一个 ConnCtx(线程活得比当前栈帧久,必须堆上),spawn 失败时关流、释放上下文再 continue;thread.detach()——每个连接独立跑完一生39。

这个模型的理由说得坦白:每连接一个阻塞 I/O 循环,没有 WouldBlock 记账,用每线程的内存开销换实现复杂度的大幅下降40。但书也给了两记生产提醒:

  • TCP_NODELAY:生产部署应给每个接受的流禁用 Nagle 算法——小缓存响应与接收方 delayed-ACK 定时器相互作用,能造成几十毫秒级的停顿;memcached、Redis、HAProxy 对每个接受的连接都设 TCP_NODELAY,正是为此41。
  • 没有优雅停机,而且书选择「辩护而不是隐瞒」:run() 是无出口的 while (true),Ctrl+C 进程瞬间蒸发,正在解析的请求被弃,监听套接字靠内核 TIME_WAIT 自然过期。标准疗法不小:全局原子标志 + SIGTERM handler + 能被打断的 accept(标志本身叫不醒阻塞在 accept 里的线程,还得安排中断、关闭或超时);且每个连接都在 detached 线程上,服务器手里没有在飞工作的句柄,想 drain 都 drain 不了——真停机得把 detached 换成共享注册表里可 join 的线程。这些每件都值一讲,但每件都偏离本章主题(缓存本身)。与其装上一个看着完整、实则悄悄丢弃在飞连接的停机路径——那还不如一个坦白的 while(true),因为它给未解的问题穿上了已解的 costume——书保留了诚实的最小循环,把缺口指名道姓留作练习42。

8. 三件工具:正确性、可用性、性能的三角

测试客户端 zemp_client:固定序列跑全部命令,四组各验一面——基础命令(PING→PONG、SET/GET 往返、missing key 回 NOT_FOUND 而非错误);TTL 惰性过期(100 ms TTL,立即 GET 有值,睡 150 ms 再 GET 得 NOT_FOUND——「没有后台线程参与,过期发生在访问那一刻」);更新与删除(原地更新不插重复项、DEL 幂等);65 000 字节大值(验证 u32 长度字段跨网络包重组)43。

CLI zcache_cli:一次调用一条命令一次结果,Unix 哲学;用户-facing 错误 std.process.exit(1) 干净退出,不带栈跟踪44。实测五连:set name Mihalis 5000 → OK;get name → Mihalis;del name → OK;再 get → (nil)45。

基准 bench.zig:三件值得一提的构造——fmtKey() 在热循环里用 32 字节栈缓冲,堆分配被彻底请出测量路径;Histogram 记每条操作的纳秒时间戳,跑完排序算 mean/min/max/p99;runPhase() 对 comptime 阶段枚举泛化(set/get/del/mix),编译器为每个变体彻底消除热循环里的分支46。mix 模式 60% SET / 30% GET / 10% DEL,模拟真实访问分布47。

9. 数字:三组,一组一个教训

第一组(串行,10 万操作,64 字节值,1000 个 key)48:

阶段 吞吐 平均 p99 max
SET 25,951 ops/s 38 µs 65 µs 162 µs
GET 18,546 ops/s 53 µs 85 µs 182 µs
DEL 35,686 ops/s 27 µs 51 µs 116 µs

图说:GET 比 SET 慢,反直觉(读不该更简单吗?)——差在响应路径:GET 命中要 encodeGetPayload() 编码 [value_len][value],分配临时堆缓冲、写连接、再释放;SET 响应是空 OK 帧,零分配。响应路径上的分配器往返就是这 15 µs 差的头号嫌疑49。DEL 最快:响应同样空载,缓存侧也最简单——找节点、摘除、释放,没有驱逐检查、没有 key/value 复制50。

两个诚实的警告:其一,基准只用 1000 个不同 key 打 10 万次操作,初始 SET 之后每次 GET 都是命中且总在 LRU 头部附近——GET 数字是上限,不是典型值;要真实,用 20 万 key 打容量 1000 的缓存,造 50% 未中率51。其二,串行往返模式每操作都付全价回环 TCP 栈开销;它在测延迟,没在测吞吐上限——流水化客户端(发多帧再读响应)才会逼出天花板52。

第二组(mix)53:28,936 ops/s、平均 34 µs、p99 63 µs——加权后向便宜的操作(60% SET、10% 最便宜的 DEL)靠拢;p99 几乎与 SET 的 65 µs 相同,说明混合负载的尾部延迟由 SET 路径主导54。

第三组(并发扩展曲线)——全章最重要的数字55。固定 20 万操作,并发 1 / 50 / 200 三档:

并发 SET 吞吐 SET 平均延迟 SET p99
1 9,227 ops/s 108 µs 134 µs
50 8,673 ops/s 4,645 µs 83,190 µs
200 8,652 ops/s 18,171 µs 157,321 µs

图说:吞吐纹丝不动,延迟爆炸——50 并发时 1% 的操作要等互斥锁几十毫秒;200 并发时平均 SET 延迟 18 ms、p99 157 ms。天花板就是那一把单互斥锁:每个连接线程的每个操作都要穿过它排队,加线程只加竞争,不加容量56。书把话说透:这组测量把「分片锁」从一个听着合理的想法变成有量化依据的下一步——不是断言锁会成为瓶颈,而是看着它发生57。

10. 加厚:服务器与协议层的四个近景

10.1 一个连接的完整一生

run() 的 accept 循环里,每条连接的生命周期被三层 defer 包成不会漏的粽子:accept 出错记 warn 继续;为连接堆分配 ConnCtx(io、分配器、流、缓存指针、缓冲尺寸五件套)失败——立即关流防文件描述符泄漏;spawn 失败——关流加销毁上下文再 continue60。connThread 接手后分配读写缓冲、调 serveClient、退出时释放上下文——线程活得比调用它的栈帧久,所以上下文必须在堆上,所有权随线程走61。thread.detach() 声明主循环永不 join,连接独立自主跑完一生62。

10.2 serveClient:两段式读循环就是全部协议

连接线程的循环体只有四行:readSliceAll 恰好读 8 字节前缀 → parsePrefix 取出声明长度 → 按长度恰好读 payload → 分发给 handleRequest,然后回到开头63。没有事件循环、没有 WouldBlock 记账——每个连接一个阻塞 I/O 循环,用每线程的内存开销换实现复杂度的大幅下降64。

10.3 响应路径的两个"恰好"

writeResponse() 在栈上拼出 8 字节前缀(magic、版本、状态码的整数值、恒零 flags、大端 u32 长度),两次 writeAll 发出——零堆分配;接受 anytype 写器,缓冲流、测试缓冲、裸套接字通吃65。GET 命中的响应要多一步:encodeGetPayload() 分配 4 + value.len 字节,写入大端长度头再拷值——这步分配是故意的:值切片可能指着缓存内部内存,不能跨锁释放持有,必须复制一份调用方可以写网、写完即丢的独立副本66。handleRequest 每个分支结束都显式 flush——客户端永远不必等在缓冲里67。

10.4 八个协议单测与幂等删除的回响

protocol.zig 的八个单测覆盖:前缀校验、坏 magic 检测、超长 payload 拒绝、每种命令的编码往返68。handleRequest 对 del 分支的检查把第 5 节的设计落到了线上:delete() 返回 false(键不存在)→ 状态码 .not_found;true → .ok——对缺失键删两次,第二次回 NOT_FOUND 而不是错误,CLI 上看到的正是这个语义69。

11. 作者的判断与证据

  • 判断: 「机械共情」——让软件逻辑对齐硬件现实——是这个设计的总纲;Zig 的精确内存管理与无隐藏运行时,保证操作确定、无 GC 背景线程带来的延迟尖峰58。证据:全章每个分配点都有配对的释放点,errdefer 护住每条失败路径。
  • 判断: GET 比 SET 慢的 15 µs 来自响应路径的分配49。证据:机制推演 + DEL(同样空响应)更快的一致性旁证。没有直接验证(没做「去掉编码再测」的对照),标注为「头号嫌疑」是诚实的。
  • 判断: 单互斥锁是并发天花板,分片锁是量出来的下一步5657。证据:三档并发的完整曲线——这是全章论证最硬的一组数据。
  • 方法论判断: 优雅停机的缺口「辩护而不隐瞒」——一个伪装成已解的半吊子方案,比坦白的 while(true) 更糟42。

11. 边界与局限

  • 服务器没有优雅停机、没有 TCP_NODELAY、没有连接上限强制(--max-clients 解析了但没执行)59。
  • 单互斥锁已被基准定量确认为瓶颈;分片锁、异步前端都在练习里,没给实现57。
  • 串行基准低估吞吐上限;GET 数字是命中率 100% 的上限5152。
  • TTL 惰性过期意味着永不再被访问的过期键永远占着容量,直到被 LRU 挤掉——没有后台清扫。
  • 三个客户端覆盖正确性/交互/性能三角,但没有模糊测试或恶意输入对抗测试(bad magic 等只验了单测)。

12. 可带走的

  1. 二进制协议三件套:packed struct 定头、大端长度、magic+版本+上限三道闸。
  2. 帧结构(静态规格)与状态机(动态拼装)分开设计;阻塞 I/O 下,调用栈就是状态机。
  3. readSliceAll + 调用栈 = 免费的部分读处理;事件驱动才需要显式记账。
  4. 协议上限(64 MiB)与 I/O 缓冲(几 KB)是两个独立设置,别混。
  5. 缓存要 O(1) 的查找 + O(1) 的驱逐:哈希表给前者,侵入式双向链表给后者。
  6. 链表不变式全押在两个原语上:detach(全程序唯一摘除点)与 attachHead(空表身兼头尾)。
  7. get 返回前在锁内复制——别名切片会在锁后悬空。
  8. 插入三次分配、errdefer 逐个兜底;freeNode 唯一释放点;先摘除再释放。
  9. 映射键指向节点自有副本,不握调用方的切片。
  10. 基准客户端自己也要 ReleaseFast、热循环零堆分配、p99 比 mean 诚实。
  11. 吞吐持平 + 延迟爆炸 = 串行点(锁)的指纹;分片前先量出这条曲线。
  12. 小响应 + delayed-ACK = Nagle 陷阱;memcached/Redis/HAProxy 都设 TCP_NODELAY。

13. 原文地图

主题 原书章 原文位置
与生产的差距清单 Chapter 10: Building a High-Performance In-Memory Cache Server text/110-ch10-chapter-10-building-a-high-performance-in-memory.txt:17(搜「sharded locking」)
ZEMP 固定宽度头动机 Wire protocol: designing a binary protocol (ZEMP) text/111-fm-wire-protocol-designing-a-binary-protocol-zemp.txt:19(搜「fixed-width」)
packed struct 无填充 Wire protocol: designing a binary protocol (ZEMP) text/111-fm-wire-protocol-designing-a-binary-protocol-zemp.txt:53(搜「hidden padding」)
帧结构与状态机分工 Wire protocol: designing a binary protocol (ZEMP) text/111-fm-wire-protocol-designing-a-binary-protocol-zemp.txt:63(搜「arbitrary fragments」)
调用栈即状态机 Implementing a state machine to parse streaming TCP data text/112-fm-implementing-a-state-machine-to-parse-streaming-.txt:55(搜「serve that role」)
显式状态是异步的代价 Implementing a state machine to parse streaming TCP data text/112-fm-implementing-a-state-machine-to-parse-streaming-.txt:69(搜「event-driven」)
部分读的线程上下文 Handling partial reads and buffer fragmentation text/113-fm-handling-partial-reads-and-buffer-fragmentation.txt:13(搜「exactly where it left off」)
两上限的分工 Learning about server implementation details text/119-fm-learning-about-server-implementation-details.txt:936(搜「independent settings」)
双结构各司其职 Why do we need both a hash map and a linked list text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:5(搜「two disparate operations」)
侵入式链表 Why do we need both a hash map and a linked list text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:61(搜「embed」)
惰性 TTL 与急迫驱逐 Storage engine: implementing the LRU cache text/115-fm-storage-engine-implementing-the-lru-cache.txt:31(搜「lazily」)
链表不变式 The doubly linked list text/116-fm-the-doubly-linked-list.txt:63(搜「easy to audit」)
detach 全程序唯一 The doubly linked list text/116-fm-the-doubly-linked-list.txt:145(搜「single place」)
映射键指向节点副本 The Hash Map wrapper text/117-fm-the-hash-map-wrapper.txt:27(搜「not the caller's slice」)
预分配防 rehash 尖峰 The Hash Map wrapper text/117-fm-the-hash-map-wrapper.txt:37(搜「ensureTotalCapacity」)
三次分配与 errdefer Memory management text/118-fm-memory-management.txt:7(搜「three allocations」)
removeNode 顺序 Memory management text/118-fm-memory-management.txt:98(搜「dangling pointer」)
单层所有权模型 Learning about server implementation details text/119-fm-learning-about-server-implementation-details.txt:89(搜「single-allocator」)
get 锁内复制 Learning about server implementation details text/119-fm-learning-about-server-implementation-details.txt:426(搜「dangle」)
LRU 驱逐单测 Learning about server implementation details text/119-fm-learning-about-server-implementation-details.txt:707(搜「LRU eviction」)
64 MiB 防内存耗尽 Learning about server implementation details text/119-fm-learning-about-server-implementation-details.txt:1017(搜「memory exhaustion」)
TCP_NODELAY Learning about server implementation details text/119-fm-learning-about-server-implementation-details.txt:1355(搜「TCP_NODELAY」)
无停机的坦白 Learning about server implementation details text/119-fm-learning-about-server-implementation-details.txt:1423(搜「costume」)
测试客户端四组 Explaining zemp_client.zig text/120-fm-explaining-zemp-client-zig.txt:81(搜「four groups」)
TTL 150ms 实测 Explaining zemp_client.zig text/120-fm-explaining-zemp-client-zig.txt:98(搜「150ms」)
CLI 一次一命令 Explaining zcache_cli.zig text/121-fm-explaining-zcache-cli-zig.txt:5(搜「one command」)
bench 的 ReleaseFast Explaining bench.zig text/122-fm-explaining-bench-zig.txt:9(搜「ReleaseFast」)
comptime 阶段枚举 Explaining bench.zig text/122-fm-explaining-bench-zig.txt:39(搜「comptime」)
GET 慢于 SET 的解释 Explaining bench.zig text/122-fm-explaining-bench-zig.txt:122(搜「empty OK frame」)
keyspace 警告 Explaining bench.zig text/122-fm-explaining-bench-zig.txt:140(搜「upper bound」)
并发曲线 Explaining bench.zig text/122-fm-explaining-bench-zig.txt:329(搜「4,645」)
单锁天花板 Explaining bench.zig text/122-fm-explaining-bench-zig.txt:341(搜「serializes」)
分片锁的量化的理由 Explaining bench.zig text/122-fm-explaining-bench-zig.txt:212(搜「quantified」)

本章拆解依据原书第 10 章(含各小节)。每条出处可用 node scripts/book-verify.mjs systems-programming-with-zig 回核。


  1. 出处:「Chapter 10: Building a High-Performance In-Memory Cache Server」第 17 段(text/110-ch10-chapter-10-building-a-high-performance-in-memory.txt:17,搜「sharded locking」)。 ↩

  2. 出处:「Learning about server implementation details」第 71 段(text/119-fm-learning-about-server-implementation-details.txt:71,搜「deliberately thin」)。 ↩

  3. 出处:「Learning about server implementation details」第 41 段(text/119-fm-learning-about-server-implementation-details.txt:41,搜「ReleaseFast」)与「Explaining bench.zig」第 15 段(text/122-fm-explaining-bench-zig.txt:15,搜「contaminate」)。 ↩

  4. 出处:「Wire protocol: designing a binary protocol (ZEMP)」第 19 段(text/111-fm-wire-protocol-designing-a-binary-protocol-zemp.txt:19,搜「fixed-width」)。 ↩

  5. 出处:「Wire protocol: designing a binary protocol (ZEMP)」第 33 段(text/111-fm-wire-protocol-designing-a-binary-protocol-zemp.txt:33,搜「8-byte prefix」)。 ↩

  6. 出处:「Wire protocol: designing a binary protocol (ZEMP)」第 53 段(text/111-fm-wire-protocol-designing-a-binary-protocol-zemp.txt:53,搜「hidden padding」)。 ↩

  7. 出处:「Learning about server implementation details」第 1009 段(text/119-fm-learning-about-server-implementation-details.txt:1009,搜「standardizes the protocol」)。 ↩

  8. 出处:「Learning about server implementation details」第 1017 段(text/119-fm-learning-about-server-implementation-details.txt:1017,搜「memory exhaustion」)。 ↩

  9. 出处:「Wire protocol: designing a binary protocol (ZEMP)」第 63 段(text/111-fm-wire-protocol-designing-a-binary-protocol-zemp.txt:63,搜「arbitrary fragments」)。 ↩

  10. 出处:「Implementing a state machine to parse streaming TCP data」第 7 段(text/112-fm-implementing-a-state-machine-to-parse-streaming-.txt:7,搜「Reading Prefix」)。 ↩

  11. 出处:「Implementing a state machine to parse streaming TCP data」第 37 段(text/112-fm-implementing-a-state-machine-to-parse-streaming-.txt:37,搜「CLOSED」)。 ↩

  12. 出处:「Implementing a state machine to parse streaming TCP data」第 55 段(text/112-fm-implementing-a-state-machine-to-parse-streaming-.txt:55,搜「serve that role」)与第 67 段(text/112-fm-implementing-a-state-machine-to-parse-streaming-.txt:67,搜「for free」)。 ↩

  13. 出处:「Implementing a state machine to parse streaming TCP data」第 69 段(text/112-fm-implementing-a-state-machine-to-parse-streaming-.txt:69,搜「event-driven」)。 ↩

  14. 出处:「Handling partial reads and buffer fragmentation」第 13 段(text/113-fm-handling-partial-reads-and-buffer-fragmentation.txt:13,搜「exactly where it left off」)与第 15 段(text/113-fm-handling-partial-reads-and-buffer-fragmentation.txt:15,搜「back to back」)。 ↩

  15. 出处:「Learning about server implementation details」第 936 段(text/119-fm-learning-about-server-implementation-details.txt:936,搜「independent settings」)与第 956 段(text/119-fm-learning-about-server-implementation-details.txt:956,搜「waste megabytes」)。 ↩

  16. 出处:「Why do we need both a hash map and a linked list」第 5 段(text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:5,搜「two disparate operations」)。 ↩

  17. 出处:「Why do we need both a hash map and a linked list」第 23 段(text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:23,搜「catastrophically」)。 ↩

  18. 出处:「Why do we need both a hash map and a linked list」第 25 段(text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:25,搜「inherently unordered」)。 ↩

  19. 出处:「Why do we need both a hash map and a linked list」第 35 段(text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:35,搜「overlaid」)与第 49 段(text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:49,搜「O(1)」)。 ↩

  20. 出处:「Why do we need both a hash map and a linked list」第 57 段(text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:57,搜「latency spikes」)。 ↩

  21. 出处:「Why do we need both a hash map and a linked list」第 61 段(text/114-fm-why-do-we-need-both-a-hash-map-and-a-linked-list.txt:61,搜「embed」)。 ↩

  22. 出处:「Learning about server implementation details」第 707 段(text/119-fm-learning-about-server-implementation-details.txt:707,搜「LRU eviction」)。 ↩

  23. 出处:「Learning about server implementation details」第 317 段(text/119-fm-learning-about-server-implementation-details.txt:317,搜「linear scan」)。 ↩

  24. 出处:「The doubly linked list」第 63 段(text/116-fm-the-doubly-linked-list.txt:63,搜「easy to audit」)。 ↩

  25. 出处:「The doubly linked list」第 145 段(text/116-fm-the-doubly-linked-list.txt:145,搜「single place」)。 ↩

  26. 出处:「The doubly linked list」第 183 段(text/116-fm-the-doubly-linked-list.txt:183,搜「empty list」)。 ↩

  27. 出处:「The doubly linked list」第 209 段(text/116-fm-the-doubly-linked-list.txt:209,搜「only two functions」)。 ↩

  28. 出处:「Learning about server implementation details」第 205 段(text/119-fm-learning-about-server-implementation-details.txt:205,搜「defer」)。 ↩

  29. 出处:「Storage engine: implementing the LRU cache」第 31 段(text/115-fm-storage-engine-implementing-the-lru-cache.txt:31,搜「lazily」)与「Explaining zemp_client.zig」第 149 段(text/120-fm-explaining-zemp-client-zig.txt:149,搜「No background thread」)。 ↩

  30. 出处:「Learning about server implementation details」第 426 段(text/119-fm-learning-about-server-implementation-details.txt:426,搜「dangle」)。 ↩

  31. 出处:「Learning about server implementation details」第 675 段(text/119-fm-learning-about-server-implementation-details.txt:675,搜「in-place」)。 ↩

  32. 出处:「Learning about server implementation details」第 683 段(text/119-fm-learning-about-server-implementation-details.txt:683,搜「preemptive eviction」)。 ↩

  33. 出处:「Memory management」第 7 段(text/118-fm-memory-management.txt:7,搜「three allocations」)与第 33 段(text/118-fm-memory-management.txt:33,搜「errdefer」)。 ↩

  34. 出处:「Learning about server implementation details」第 500 段(text/119-fm-learning-about-server-implementation-details.txt:500,搜「intentional design choice」)。 ↩

  35. 出处:「Memory management」第 98 段(text/118-fm-memory-management.txt:98,搜「dangling pointer」)。 ↩

  36. 出处:「Memory management」第 120 段(text/118-fm-memory-management.txt:120,搜「key first」)。 ↩

  37. 出处:「The Hash Map wrapper」第 27 段(text/117-fm-the-hash-map-wrapper.txt:27,搜「not the caller's slice」)。 ↩

  38. 出处:「The Hash Map wrapper」第 37 段(text/117-fm-the-hash-map-wrapper.txt:37,搜「ensureTotalCapacity」)。 ↩

  39. 出处:「Learning about server implementation details」第 1329 段(text/119-fm-learning-about-server-implementation-details.txt:1329,搜「outlives」)。 ↩

  40. 出处:「Learning about server implementation details」第 1347 段(text/119-fm-learning-about-server-implementation-details.txt:1347,搜「thread-per-connection」)。 ↩

  41. 出处:「Learning about server implementation details」第 1357 段(text/119-fm-learning-about-server-implementation-details.txt:1357,搜「Nagle」)。 ↩

  42. 出处:「Learning about server implementation details」第 1423 段(text/119-fm-learning-about-server-implementation-details.txt:1423,搜「costume」)。 ↩↩

  43. 出处:「Explaining zemp_client.zig」第 81 段(text/120-fm-explaining-zemp-client-zig.txt:81,搜「four groups」)。 ↩

  44. 出处:「Explaining zcache_cli.zig」第 43 段(text/121-fm-explaining-zcache-cli-zig.txt:43,搜「std.process.exit」)。 ↩

  45. 出处:「Explaining zcache_cli.zig」第 70 段(text/121-fm-explaining-zcache-cli-zig.txt:70,搜「PONG」)。 ↩

  46. 出处:「Explaining bench.zig」第 25 段(text/122-fm-explaining-bench-zig.txt:25,搜「hot loop」)、第 31 段(text/122-fm-explaining-bench-zig.txt:31,搜「p99」)与第 39 段(text/122-fm-explaining-bench-zig.txt:39,搜「comptime」)。 ↩

  47. 出处:「Explaining bench.zig」第 59 段(text/122-fm-explaining-bench-zig.txt:59,搜「60%」)。 ↩

  48. 出处:「Explaining bench.zig」第 73 段(text/122-fm-explaining-bench-zig.txt:73,搜「3853」)。 ↩

  49. 出处:「Explaining bench.zig」第 122 段(text/122-fm-explaining-bench-zig.txt:122,搜「empty OK frame」)。 ↩↩

  50. 出处:「Explaining bench.zig」第 130 段(text/122-fm-explaining-bench-zig.txt:130,搜「no eviction check」)。 ↩

  51. 出处:「Explaining bench.zig」第 140 段(text/122-fm-explaining-bench-zig.txt:140,搜「upper bound」)。 ↩↩

  52. 出处:「Explaining bench.zig」第 148 段(text/122-fm-explaining-bench-zig.txt:148,搜「throughput ceiling」)。 ↩↩

  53. 出处:「Explaining bench.zig」第 181 段(text/122-fm-explaining-bench-zig.txt:181,搜「3455」)。 ↩

  54. 出处:「Explaining bench.zig」第 194 段(text/122-fm-explaining-bench-zig.txt:194,搜「tail latency」)。 ↩

  55. 出处:「Explaining bench.zig」第 323 段(text/122-fm-explaining-bench-zig.txt:323,搜「clean baseline」)。 ↩

  56. 出处:「Explaining bench.zig」第 341 段(text/122-fm-explaining-bench-zig.txt:341,搜「serializes」)。 ↩↩

  57. 出处:「Explaining bench.zig」第 212 段(text/122-fm-explaining-bench-zig.txt:212,搜「quantified」)。 ↩↩↩

  58. 出处:「Explaining bench.zig」第 373 段(text/122-fm-explaining-bench-zig.txt:373,搜「mechanical sympathy」)。 ↩

  59. 出处:「Explaining bench.zig」第 459 段(text/122-fm-explaining-bench-zig.txt:459,搜「never enforced」)。 ↩

  60. 出处:「Learning about server implementation details」第 1331 段(text/119-fm-learning-about-server-implementation-details.txt:1331,搜「file descriptor leak」)。 ↩

  61. 出处:「Learning about server implementation details」第 1329 段(text/119-fm-learning-about-server-implementation-details.txt:1329,搜「outlives」)。 ↩

  62. 出处:「Learning about server implementation details」第 1341 段(text/119-fm-learning-about-server-implementation-details.txt:1341,搜「detach」)。 ↩

  63. 出处:「Learning about server implementation details」第 1217 段(text/119-fm-learning-about-server-implementation-details.txt:1217,搜「reads exactly 8 bytes」)。 ↩

  64. 出处:「Learning about server implementation details」第 1347 段(text/119-fm-learning-about-server-implementation-details.txt:1347,搜「straightforward」)。 ↩

  65. 出处:「Learning about server implementation details」第 1034 段(text/119-fm-learning-about-server-implementation-details.txt:1034,搜「8-byte prefix」)。 ↩

  66. 出处:「Learning about server implementation details」第 1125 段(text/119-fm-learning-about-server-implementation-details.txt:1125,搜「independent copy」)。 ↩

  67. 出处:「Learning about server implementation details」第 570 段(text/119-fm-learning-about-server-implementation-details.txt:570,搜「never waits」)。 ↩

  68. 出处:「Learning about server implementation details」第 928 段(text/119-fm-learning-about-server-implementation-details.txt:928,搜「Eight unit tests」)。 ↩

  69. 出处:「Learning about server implementation details」第 566 段(text/119-fm-learning-about-server-implementation-details.txt:566,搜「not_found」)。 ↩