搜索 K
Appearance
本文由 AirPick 实验室在 2026 年 Q1 于 x86 软路由(N100 / 16G)、ARM 路由器(MT7986 / 512M)、macOS 15(M4)、Windows 11 四类环境下实测复现,所有量级数据均为同一配置下的重复采样中位数,用于说明差距方向,不作为绝对性能承诺。
.srs 就别用 JSON 内联规则。同一份 GeoSite 数据,JSON 源文件与 srs 二进制在内存增量与冷启动耗时上通常差一个数量级,在 512M 内存的硬路由上这是"能用"和"OOM 重启"的分界线。route 初始化拖死。以一次浏览器访问 www.example.cn 为例,sing-box 在 Headless 模式下的完整决策链路是:
dns.rules 选出 resolver,决定这次解析走直连 DNS 还是代理解析(这一步极易泄漏);route.rules 从上到下线性遍历,每条规则内部再对 rule_set 做集合匹配;outbound 后建立连接,若目标出站是 selector/urltest,还要触发一次组内健康检查。规则集只影响第 3 步,但它往往决定第 2、4 步的走向。 一份把 geosite-cn 错配到代理出站的规则集,会让国内视频站走一趟海外 IEPL 再回来,延迟从 20ms 级飙到 200ms 级——这不是内核的锅,是规则集的锅。
.srs 并不是"把 JSON 压缩了一下"这么简单。它的核心设计取向有三条:
mmap + 一次线性扫描;domain_suffix 从"一堆字符串"变成"一棵树";update_interval 独立刷新,不必因为一条规则更新就重载整份配置。编译动作可以在任意一台机器上离线完成,产物拷进路由器即可,完全不需要目标设备具备编译能力——这点对 ARM 硬路由非常关键。
在 JSON 内联规则时代,一条 domain_suffix 规则往往包含数万个条目,匹配时是逐条字符串比对,复杂度与规则条数线性相关。而 srs 编译后的域名匹配走的是前缀树路径,复杂度约等于 域名标签数(通常 2~4),基本与集合大小解耦。
这就是为什么:
domain_regex 这种必须走正则引擎的规则上,编译没有任何帮助,它能不写就不写;route.rules 的条数)**上,而不是单条规则集内部——所以"规则集顺序"比"规则集大小"更值得优化。带 GUI 的客户端通常有几百 MB 内存余量、有后台线程做异步加载、有用户手动点击"重载配置"。而 Headless(软路由、NAS、Docker、VPS)面临的是:
route 初始化会阻塞,TUN 起来了但不通,表现为"整个网络挂了";所以 Headless 的规则集策略必须是 本地优先 + 远端可选 + 失败可降级。
下表为同一份 GeoSite-CN 量级数据(约 8.6 万条域名后缀)在四类形态下的量级对照,用于说明相对关系:
| 量化指标 | 内联 JSON 规则 | 远程 JSON rule-set | 本地编译 srs | 远程 srs + 缓存 |
|---|---|---|---|---|
| 单集合磁盘体积 | 12~35 MB | 3~9 MB(gzip 后 0.8~2 MB) | 0.6~2.5 MB | 同左(下载后落盘) |
| 冷启动加载耗时(N100) | 900~2600 ms | 700~2000 ms | 35~120 ms | 40~150 ms |
| 冷启动加载耗时(MT7986) | 4~11 s | 3~9 s | 120~400 ms | 150~500 ms |
| 常驻内存增量 | 180~420 MB | 150~380 MB | 12~45 MB | 12~45 MB |
| 单次域名匹配 p99 | 80~400 µs | 80~400 µs | 3~15 µs | 3~15 µs |
| 规则更新流量 | 全量重载配置 | 全量重下 | 需重新分发文件 | 增量下载 0.5~3 MB |
| 首次解析 CPU 尖峰 | 高(长时占满单核) | 高 | 低 | 低 |
| 离线可用性 | 完全可用 | 不可用 | 完全可用 | 首次需联网 |
| 可版本化 diff | 差(文本巨大) | 差 | 好(二进制体积小) | 好 |
| 移动端适用性 | 差 | 中 | 好 | 好 |
读表要点:真正产生数量级差异的是"JSON 解析 vs 二进制反序列化",而不是"本地 vs 远程"。远端 srs 与本地 srs 的性能几乎一致,差别只在首次联网依赖与更新控制权。
| 人群画像 | 设备与内存 | 推荐方案 | 关键理由 |
|---|---|---|---|
| 硬路由玩家 | MT7986 / 512M | 本地编译 srs + 手动分发 | 内存是硬约束,远端拉取失败即断网 |
| 软路由 / NAS | N100 / 8G+ | 远程 srs + 本地缓存 | 内存宽裕,追求规则新鲜度 |
| 桌面用户 | macOS / Windows | 客户端内置规则集 + 少量精确域名 | 无需折腾,避免规则集膨胀 |
| 移动端用户 | iOS / Android | 精简自定义 srs(< 1 MB) | 省电、省内存、减少后台被杀 |
| 企业出口 / 多用户 | VPS / 容器 | 自建规则集分发 + Headless systemd | 需要统一策略与可审计更新 |
需要说明的是,规则集优化解决的是"分流准确性"和"设备资源占用",它完全不改变物理链路质量。如果你的痛点是晚高峰 20:00 后 4K 卡顿、YouTube 缓冲圈转不停,那属于带宽争抢与线路拥塞问题,得从出口资源上解决。
拿到 GeoSite JSON 源文件后,离线编译:
# 编译
sing-box rule-set compile geosite-cn.json -o geosite-cn.srs
# 校验产物(反编译回 JSON 检查条目是否丢失)
sing-box rule-set decompile geosite-cn.srs -o geosite-cn.check.json
# 多集合合并(把广告拦截与国内直连合并成一份,减少文件句柄)
sing-box rule-set merge geosite-cn.srs geosite-category-ads-all.srs -o direct.srs避坑点:compile 对源文件格式要求严格。若你的 JSON 是旧版 geosite.dat 导出的结构(带 code 与 rules 顶层数组),需要先用 rule-set convert 转换,直接 compile 会报结构错误。
{
"route": {
"rule_set": [
{
"type": "local",
"tag": "direct-cn",
"format": "binary",
"path": "/etc/sing-box/rules/direct.srs"
},
{
"type": "remote",
"tag": "geosite-ads",
"format": "binary",
"url": "https://your-mirror.example/geosite-ads.srs",
"download_detour": "direct",
"update_interval": "168h"
}
],
"rules": [
{ "rule_set": ["geosite-ads"], "action": "reject" },
{ "rule_set": ["direct-cn"], "outbound": "direct" },
{ "protocol": "dns", "action": "hijack-dns" },
{ "ip_is_private": true, "outbound": "direct" }
],
"final": "proxy"
}
}三个高频错误:
format 写成 "source" 却指向 .srs 文件,运行时直接报反序列化失败;download_detour 指向尚未就绪的出站,形成循环依赖,表现为远端规则集永远下载超时;reject 规则放在 direct 之后,导致广告域名先被直连出去再被拦截,实际已经建立连接。匹配成本主要由 rules 数组条数决定。经验排序:
geosite-cn、geosite-geolocation-cn);final 交给 outbound,不要写多余规则)。把命中率最高的规则放前面,比把集合编小更能降延迟。
LimitNOFILE 至少设到 1048576,TUN 场景下默认 1024 会在大流量时出现 too many open files;scutil --dns 会看到系统 DNS 与 sing-box 抢答;route 中加 ip_is_private 直连兜底;/sdcard 读取权限问题;[Unit]
Description=sing-box service
After=network-online.target nss-lookup.target
Wants=network-online.target
[Service]
Type=simple
User=sing-box
ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json
Restart=on-failure
RestartSec=5s
LimitNOFILE=1048576
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/etc/sing-box/rules /var/cache/sing-box
[Install]
WantedBy=multi-user.target关键点:
After=network-online.target 而非 network.target,否则远端规则集会在网络未就绪时下载失败;Restart=on-failure 配合 RestartSec,避免崩溃后疯狂重启刷爆日志;ReadWritePaths 显式放行规则集缓存目录,ProtectSystem=strict 才拦不住自己。sing-box check -c /etc/sing-box/config.json
sing-box format -c /etc/sing-box/config.json -wcheck 只做语法与引用校验,不会验证远端 URL 是否可达。所以远端规则集必须有本地回退:把一份最小可用的 fallback.srs 放在本地,配置里保留 local 类型条目,远端失败时降级不掉线。
容器场景注意两点:一是规则集目录必须挂载为卷,否则每次重建容器都要重新下载(3MB × N 个集合,很痛);二是 TUN 需要 --cap-add=NET_ADMIN --device /dev/net/tun,否则容器起来但流量不接管。
排查分流问题必须先分层:是链路不通、DNS 错了,还是规则集匹配错了?三者现象高度相似。
# 1. 配置层:规则集是否加载成功
sing-box check -c /etc/sing-box/config.json
journalctl -u sing-box -n 200 --no-pager | grep -i "rule_set\|rule-set"
# 2. DNS 层:macOS 看系统 DNS 是否被抢答
scutil --dns | head -40
# 3. DNS 层:直查 sing-box 内置 resolver
dig @127.0.0.1 -p 5353 www.example.cn +short
dig @127.0.0.1 -p 5353 www.google.com +short
# 4. 链路层:目标 IP 的路径与丢包
mtr -rwzc 100 1.1.1.1
mtr -rwzc 100 www.example.cn
# 5. 端口层:确认出站是否真的通
tcping -t 3 your-server.example 443
nc -vz -w 3 your-server.example 443
# 6.