Skip to content

设计取舍 ​

这一页记录「为什么是现在这个样子」,以及哪些看起来该做、量过之后决定不做的事。所有判断都基于实测,条件变了就该重新评估。

先看负载,再谈性能 ​

截至 2026-10-01 的 7 天:

指标数值
查询量约 13.5 万次(约 0.2 次/秒)
mosproxy 缓存命中90.1%
国内递归9.6%,平均 311 ms
境外递归0.03%
解析失败0.32%

结论:

  • DNS 解析路径上不跑 Go 代码。查询经 mosproxy 和 Unbound 处理,Go 只负责生成数据、采集和管理。优化 Go 代码不会让解析变快。
  • 但采集器能让解析变慢。mosproxy 在回应客户端之前、拿着一把全局锁把查询日志同步写进管道,采集器在管道另一头。采集器一旦读得比写得慢,管道写满,所有查询都卡在写日志上。所以读管道的一端从不等待,写库跟不上就丢行计数(见 查询日志)。在开发机上把库灌到 200 万行,采集器每秒能落库约 3,600 行,是现在流量的上万倍;这条保护针对的是磁盘卡顿、数据库被锁、清理积压这类瞬时停顿,不是吞吐。
  • 真正的约束是国内机器的 1.8GB 内存。每个批处理单元都设了 systemd MemoryHigh / MemoryMax;新的重计算先在 CI 或本机用真实数据量测峰值,再上生产。CI 里有一道 256MB 的规模闸门专门卡分流数据生成。
  • 性能假设必须先量:曾经断言能快 5–20 倍的改动,实测只有 1.07 倍。

隧道断了宁可失败 ​

境外查询在隧道断开时会显式失败,而不是回落直连。回落直连拿到的是被污染的答案:看着解析成功,实际更危险。这是用可用性换正确性,而且是有意的。

只做 IPv4 出口 ​

分流集合 direct4 和 WireGuard 隧道都只有 IPv4。所以国内节点的 Unbound 配置 do-ip6: no:不经 IPv6 向权威发查询(AAAA 记录照常解析,那是数据,和传输协议无关)。

这一条有过教训。线上配置曾经是 do-ip6: yes,而国内机器根本没有 IPv6 路由。对 IPv6 地址很多的区域(例如 pool.ntp.org 有 30 多个 IPv6 权威地址),Unbound 会把每次查询的 32 次发送预算全部花在不可达的 IPv6 地址上,然后返回 SERVFAIL。7 天里全部解析失败的 43% 来自这一个原因。修复后自检多了一项「Unbound 运行配置与模板一致」,用来发现线上配置和仓库模板走散。

等家里设备的 IPv6 流量真的多起来,需要的是 direct6 和带 IPv6 的隧道,而不是简单打开 do-ip6。

关掉 QNAME 最小化 ​

2026-10-05 在生产上 A/B:12 个国内常见域名,每次换一个随机 /24 子网逼 Unbound 重新问权威,开、关交替各跑两轮,再把顺序反过来跑一遍排除缓存预热的影响。

p50p90
开(默认)139–155 ms655–779 ms
关80–91 ms267–391 ms

两种顺序下结论一致:关掉之后没命中缓存的查询快一倍左右,长尾快两倍多。代价是根和顶级域服务器能看到完整的查询名——而这里问根和大部分顶级域本来就走香港出口,对方看到的是解析器的地址,不是哪台设备。所以模板里是 qname-minimisation: no,想要回去改这一行即可。

ECS 按子网分缓存 ​

客户端经常同时在两个网络里(家里宽带、手机流量),mosproxy 按「省份 + 运营商」给缓存分片,同一个名字每个分片要各问一次。实测近半数名字恰好被问两次。这是有意的:不同运营商拿到的 CDN 节点不一样,合并分片会把电信用户的答案给移动用户。Unbound 给所有答案都回非零 scope(包括根本不认 ECS 的权威),没法安全地挑出「和地区无关」的答案来共享,所以不做。

量过之后决定不做的事 ​

想法实测结论
境外查询改为转发给香港递归,而不是本机递归 + 隧道冷缓存下快 37%–50%受益的只有 0.03% 的查询,而且要改动最脆弱的 ECS 链路。不做
第二个境外出口做互备香港是单点,每月例行重启一次境外解析占比极低,重启期间有降级与告警兜底。可用性真的变重要时再做
查询日志按来源 IP 筛选—违背「不记录客户端 IP」的隐私设计。不做
黑名单、访问控制加备注字段生产上两份清单都是空的没有使用场景。不做
调大 Unbound infra 缓存统计里 9,992 / 10,000 看着满了,但有效条目只有 1,588,其余是待淘汰的过期项不是瓶颈。不改
mosproxy 缓存落盘、重启后恢复重启只丢 mosproxy 那一层,Unbound 的缓存还在;生产上一个月重启不到一次收益太小,不值得多一份代码。不做
面板统计改读分钟级汇总表13.5 万行(现在的规模)时 24 小时趋势 17 ms、域名页汇总 15 ms;灌到 200 万行时分别是 300 ms 和 450 ms汇总表是第二份事实,删单条记录、删域名、清理、清旧架构、清空统计五条路径都要同步改它,漏一条数字就对不上。表涨到百万行、或面板接口明显变慢时再做
删掉 route / rcode / exit_path 三个索引换写入速度趋势图、失败统计、出口统计都在用它们写入速度离瓶颈差四个数量级。不删
域名搜索换 FTS5 或倒序域名列13.5 万行时搜一个不存在的词 21 ms不做
前端换 React / 图表库实时页卡顿的根源是每条消息都改一次 DOM、每行一段 1.6 秒的高亮动画、一张表常驻 300 行;全是写法问题,换框架解决不了。趋势图只有 72 个点不换。仍是无构建的原生 JS:72 个点的趋势图用手写 SVG;1 万行的日志用固定行高的虚拟列表,只画看得见的几十行
实时推送改成服务端单一轮询再广播每个连接每秒一次按主键范围的查询,不到 1 ms;通常只开一两个页面不做

测试与守卫怎么留 ​

  • 保留测「两边是否对得上」的契约测试:前端调用的接口后端有没有、开发 mock 与后端字段、危险操作有没有确认说明。它们对应过真实事故,也不限制具体写法。
  • 不写钉死某种写法、某句文案的文本扫描测试。换一种同样正确的写法就误报的测试,是负担不是保护。
  • 新守卫写完要把缺陷种回去,确认它真会报警。这个项目里多次出现「测试是绿的,但不是因为它测的那件事」。

只有两台机器:一台国内,一台香港。