把 DNS 库跑快:每个查询省 12% CPU 的 BEAM 二进制技巧
来源:blog.dnsimple.com — 2026-10-19
📋 概述
DNSimple 的托管 DNS 分两层:全球 anycast 的缓存边缘先回答已缓存的部分,其余全部落到持有每个 zone 的权威服务器——也就是他们开源的 Erlang DNS 服务器 erldns。每个 DNS 查询都会两次穿过 dns_erlang:解码请求一次、编码响应一次,所以解析器就是这条链上的热代码;在每秒几千查询时无人在意,但在攻击下或规模上来后,每个用在字节与 term 之间转换的纳秒都是从下一个查询里偷走的。他们花几个月重写了 dns_erlang 的大部分热路径,在生产形态的负载下:每查询 CPU 降低 12%,饱和前每秒可服务的查询数提升 14%,且只用库层面改动,没有 NIF、没有 C、没有奇特工具链。文章先给 BEAM 二进制的五分钟速成:数据不可变,但编译器、JIT 与运行时会在能证明「没人会察觉」时复用内存、原地写入;64 字节以内的二进制放在进程堆上,更大的在进程外共享引用;匹配 <<Header:12/binary, Rest/binary>> 不复制数据,Rest 只是指向原二进制的小 term,递归匹配时编译器会保留一个 match context 游标,只有让你让它逃逸时才物化 sub-binary;而 <<Acc/binary, C>> 这种追加也不像想象中那样二次复杂度,因为运行时能证明 Acc 只有本循环持有,于是首次分配预留容量、后续原地写入。
🔑 核心要点
收益被量化:生产形态负载下每查询 CPU 降 12%,饱和前每秒查询数升 14%,全部来自库层改动,不含 NIF 与 C
为什么解析器是热代码 :每个查询到达权威服务器都要过 dns_erlang 两次,一次解码请求、一次编码响应
二进制切分几乎免费 :匹配 <<Header:12/binary, Rest/binary>> 不复制,Rest 是指向原始二进制的小 term
递归匹配时编译器保留 match context 游标直接走字节 ,只有让 Rest 逃逸(返回或存进 tuple)才会物化 sub-binary
追加并非二次复杂度:<<Acc/binary, C>> 首次追加会预留容量,之后原地写入,前提是运行时能证明只有本函数持有它
文章的落点是方法论:大部分技巧的本质是写出让编译器与 JIT 容易完成「不可变性不会被察觉」证明的代码
💡 金句
on a production-shaped workload, our nameserver now spends 12% less CPU per query and serves 14% more queries per second before saturating, from library changes alone
👍 0
❤️ 点赞
👎 0
沉底
← 返回 Lobsters 首页