← 返回博客

2026年OpenClash规则集运维完全指南:Rule-Set自动更新、分流命中率验证与误分流排查

📌 一分钟总结

规则集配好只是开始。本文讲 OpenClash 规则集的长期运维:自动更新失败的排查、缓存落盘位置、用内核 API 统计分流命中率并做基线告警、GEO 数据版本漂移,以及六种典型误分流的定位方法。

大部分人调 OpenClash 的力气都花在「配置」上:策略组怎么设计、规则怎么写、节点怎么选。但一台已经跑顺的路由器在三个月后开始出问题的原因,往往不是配置写错了,而是规则集这个"活"的部件没人管——上游规则源天天在合 PR,内核版本一直在往前走,你的用法也在变(新追的剧、新买的 AI 服务、家里新添的设备),而规则集从装好那天起就没再看过一眼。

这篇只讲运维侧的三件事:规则集怎么自动更新、怎么用量化手段确认它真的在按你的意图分流、以及分流错了怎么定位到具体哪一条。全程围绕一个判断标准:别把「我配了」当成「它生效了」。顺带说清楚一件事——验证命中率需要一条稳定的对照出口,抖动小的线路(比如 自由猫 这类 IEPL 专线)能把「规则判错」和「线路本身在抖」分开;没有稳定对照,你会花一整晚怀疑规则,其实锅在出口。

一、先搞清楚:规则集维护到底在维护什么

规则集(Rule-Set)本质是「把一大坨分流规则放进一个远程文件,内核按需拉取并按需加载」。它比把几千行规则硬写进 config 好在两个字:可维护。代价是它引入了三条会自己变化的外部输入,这三条就是运维的全部对象。

  • 规则源会变。主流规则库每天都在合 PR:新域名、失效域名、被误伤的国内域名。你今天看到「正确」的分流,可能一周后因为上游把某个域名从直连挪到代理而变成「错误」——而且没人通知你。
  • 内核会变。mihomo 的字段和默认值一直在演进。规则集格式也是:精简二进制格式 mrs 只支持 domain / ipcidr 两种 behavior,你要是拿旧的 classical 规则文件去配 format: mrs,内核直接不认。
  • 你的用法会变。最典型的是家里新买一台设备、新开一个账号(跨境电商、流媒体分区、AI 订阅),或者某天开始用某个只在特定地区能用的工具。这些需求出现的时候,规则集本身不会自动长出对应条目。

所以规则集运维是一个闭环:更新 → 验证 → 回滚。缺了"验证",你会更新一堆自己没确认过的东西;缺了"回滚",一次坏更新就能让你在晚上十点爬梯子改路由器。至于策略组怎么设计、自定义规则怎么写这些配置侧的事,在 OpenClash 进阶配置那篇里已经讲透了,本文默认你已经有一份能跑的配置,只处理它之后的长期运行问题。

二、Rule-Set 的四种托管方式:先选对形态

在动手写自动化之前,先确认你的规则集用哪种托管。mihomo 的 rule-providers 里 type 只有三个值 http / file / inline,但实际生产里常见的组合是四种,它们的运维成本差别很大。

托管方式写法更新靠谁适用场景运维风险
远程订阅(http)type: http + url + interval内核按 interval 自动拉主流规则库(社区维护,条目几千到几万)拉到不了就退化成"上次的缓存"或"没规则"
本地文件(file)type: file + path你自己(脚本/同步工具)自建规则、公司内网域名、需要人工审的规则没人推 = 永远不更新
内嵌(inline)type: inline + payload跟着 config 走几十条的少量自定义规则条目多了 config 难读,改一次要动主配置
mrs 精简包format: mrs(配 http 或 file)同上路由器等小内存设备,靠二进制格式省内存只支持 domain / ipcidr,格式错直接加载失败

一个决定性细节:behavior 必须和文件真实格式一致,domain、ipcidr、classical 三选一,写错不会报"格式不符"这么友好,而是表现为规则集加载出来是空的或规则数量明显少于预期。format 默认是 yaml,可选 yaml / text / mrs;要把已有的规则转成 mrs,官方给的转换命令是:

# 官方转换方式:domain 或 ipcidr 类型的规则文件 → mrs
mihomo convert-ruleset domain yaml proxy.yaml proxy.mrs
mihomo convert-ruleset ipcidr text cncidr.txt cncidr.mrs

如果你用的是软路由上的小内存设备,mrs 带来的内存收益是实打实的;但代价是以后不能随意换规则源——很多社区规则库只提供 yaml,换成 mrs 要自己定期转换并托管,这一步很难自动化到"零维护"。相关取舍(内存占用、加载模式、硬件转发)可以对照软路由性能优化那篇的实测数据一起看。

三、自动更新的真相:interval 是"检查周期",不是"成功保证"

加了 interval 不等于规则集每天都会更新成功。interval 的单位是秒,语义是"内核启动后,每隔这么久去检查一次远程文件是否需要更新"——它是检查周期,不是成功保证。能不能真的拉到,取决于下载通道。

这里有一个必须理解的字段:proxy。rule-providers 里的 proxy 指定这个规则集通过哪个出口下载。它存在的原因很实际——很多规则集的 URL 托管在 GitHub 上,而路由器本身的直连通道未必能稳定访问它。如果不指定 proxy,内核会用默认方式(通常是直连)去拉,拉了三次都超时,你的规则集就永远停在第一天。

更值得警惕的是死锁场景:规则集要通过代理下载,而"该走哪个代理"本身又是由规则决定的。首次部署时本地还没有缓存,如果规则集拉不下来,规则里的 RULE-SET 条目全部失效,流量直接落到最后的 MATCH——表现就是"全屋所有流量都走同一个出口",而不是报错。所以 proxy 一定要指向一个不依赖规则集的固定出口(直连、或者一个写在 proxies 里的固定节点),不要指向靠规则集才能选路的策略组。如果你的网络环境里直连通道本身就不稳,让规则集走一条不掉线的中转/专线出口是更省心的选择,双入口的机场在这方面余量更大——直连断了还能切专线,规则集不会因为一次通道故障而卡在旧版本(这也是 万达云 这种中转 + 专线双入口方案在运维场景下的实际价值)。

一份完整的远程规则集配置长这样,字段含义都在注释里:

rule-providers:
  # 国外域名:走代理(behavior 与文件格式必须一致)
  proxy:
    type: http
    url: "https://your-mirror.example.com/rules/proxy.yaml"
    path: ./rule_provider/proxy.yaml     # 相对内核工作目录,见第四节
    behavior: domain
    format: yaml
    interval: 86400                     # 秒,检查周期;86400 = 每天
    proxy: DIRECT                       # 用哪个出口拉这个文件(关键字段)
    size-limit: 8388608                 # 上限 8MB,防止拉到一个异常大文件
    header:                             # 私有托管仓库可以带鉴权
      User-Agent: ["mihomo/1.19.0"]
      Authorization: ["Bearer YOUR_TOKEN"]
  # 广告拦截:命中即拒绝
  reject:
    type: http
    url: "https://your-mirror.example.com/rules/reject.yaml"
    path: ./rule_provider/reject.yaml
    behavior: domain
    format: yaml
    interval: 86400
    proxy: DIRECT
  # 国内 CIDR:直连
  cn-ip:
    type: http
    url: "https://your-mirror.example.com/rules/cn-ip.yaml"
    path: ./rule_provider/cn-ip.yaml
    behavior: ipcidr
    format: yaml
    interval: 86400
    proxy: DIRECT

几个容易忽略但很值钱的字段:size-limit 默认 0(不限),设一个上限可以避免"镜像站返回了一个 HTML 错误页"被当成规则文件加载;path 是本地缓存的落点,决定了下次启动时能不能直接用旧文件顶一阵;header 支持自定义请求头,私有对象存储或需要 token 的镜像靠它。

四、规则集文件落在哪:目录、缓存与"重启就没了"的坑

OpenClash 在把配置交给内核之前,会做一件你未必知道的事:它会自动改写 rule-providers 里每一项的 path,统一整理成 ./rule_provider/<文件名> 的形式(proxy-providers 同理,整理成 ./proxy_provider/)。你在 LuCI 界面里写 path 或者不写,最后落到内核手里的都是这个形式。

由于 ./ 是相对内核工作目录解析的,而 OpenClash 的运行时配置位于 /etc/openclash/,结论就是:规则集的本地缓存实际在 /etc/openclash/rule_provider/ 下。知道这一点,很多问题立刻可查:

# 看规则集缓存:文件在不在、多大、上次更新到什么时候
ls -lh /etc/openclash/rule_provider/

# 单看时间戳:判断 interval 到底有没有生效
stat -c '%y  %s  %n' /etc/openclash/rule_provider/*.yaml

# 粗略数一下条目数(domain 类规则集一行一条)
wc -l /etc/openclash/rule_provider/*.yaml

这里有一条只在"小闪存设备"上才暴露的坑:OpenClash 对小闪存路由(small_flash_memory 打开时)会把 GEO 数据库一类的下载缓存放到 /tmp/etc/openclash。而 /tmp 是内存盘,重启即清空。结果是路由器每次重启都要重新下载一遍规则/GEO 数据——如果那个时刻你的出口恰好不通,重启后的分流就会以"规则缺失"的状态运行,直到下一次下载成功。判断方法很简单:重连一次路由器电源,然后 ls -lh 看你以为"每天自动更新"的目录,时间戳是不是刚刚。

另外两点务实的建议。第一,把 rule_provider 目录纳入备份:它是纯缓存,但里面可能包含你自建的本地规则集(type: file 的那部分),这些是你手写的资产,丢了要重写。第二,注意缓存文件的权限与只读性:如果某次操作把文件改成了内核进程不可写的状态,更新会静默失败,日志里只有一行不容易看见的报错。

五、命中率量化:三个数据源,从粗到细

到这里进入本文的重点:怎么知道规则真的生效了。OpenClash 给了三个可用的数据源,精度依次上升。

数据源一:内核日志。把 log-level 调到 info(排障时用 debug),mihomo 会为每个连接打一行结构化日志,格式大致是:

[TCP] 198.18.0.1:65147(chrome.exe) --> example.com:443 match DomainKeyword(example) using 主用出口[节点名]

这一行里包含四个关键信息:连接来源进程、目标域名、命中的规则类型与匹配值(DomainKeyword(example))、以及最终落在哪个策略组和哪个节点(using 主用出口[节点名])。"match … using …" 的格式就是命中归因。OpenClash 会把内核日志写到 /tmp/openclash.log,LuCI 的「运行日志」页面读的也是它。它的局限是只记录"开始建立连接"的时刻,长连接不会反复出现,靠它统计出来的命中分布是"新连接分布"而不是"流量分布"。

数据源二:RESTful API 的 /connections。这是最精确的一层。内核暴露的控制器 API 里,/connections 返回当前所有活跃连接的快照,每条连接都带走行到当前的完整信息,其中就包括 rule(命中的规则类型)、rulePayload(规则的具体参数)、chains(经过的策略组与节点链)、以及上下游字节数。给控制器配一个 secret 之后,任何能访问到路由器 9090 端口的设备都可以拉这份数据:

# 在路由器本地取一份快照(配置里 external-controller 通常是 127.0.0.1:9090)
curl -s -H "Authorization: Bearer YOUR_SECRET" http://127.0.0.1:9090/connections | head -c 400

# 只看当前连接的命中规则分布(需要 jq)
curl -s -H "Authorization: Bearer YOUR_SECRET" http://127.0.0.1:9090/connections \
  | jq -r '.connections[] | "\(.rule)\t\(.rulePayload)"' | sort | uniq -c | sort -rn | head -20

用 Python 做同样的事情更省事,也方便把结果存成历史记录(可以放在家里常驻的 Linux 机器或 NAS 上跑,客户端的规则集管理思路在 Linux 客户端那篇里有对照写法):

# 统计当前活跃连接的规则命中分布:哪条规则在真干活,一眼可见
import json, os, urllib.request, collections

CTRL = os.environ.get("CLASH_API", "http://127.0.0.1:9090")   # 路由器 API 地址
SECRET = os.environ.get("CLASH_SECRET", "")                    # 控制器 secret

req = urllib.request.Request(f"{CTRL}/connections")
if SECRET:
    req.add_header("Authorization", f"Bearer {SECRET}")
data = json.load(urllib.request.urlopen(req, timeout=8))

counter = collections.Counter()
for conn in data.get("connections", []):
    rule = conn.get("rule") or "MATCH"
    payload = conn.get("rulePayload") or "-"
    counter[(rule, payload)] += 1

total = sum(counter.values()) or 1
for (rule, payload), n in counter.most_common(25):
    print(f"{n:5d}  {n * 100 / total:5.1f}%  {rule}({payload})")

数据源三:/logs 与 /traffic。/logs 是流式日志(支持 level=info|debug|warn|error 过滤),适合做"实时盯盘";/traffic 每秒推一次上下行增量,用来判断某个策略组的流量占比。把 /traffic 的流量增量和 /connections 的规则分布结合,才能回答"哪条规则占用了多少真实流量"这个问题。

三个数据源里,/connections 是唯一能同时给出「规则 → 策略组 → 节点 → 字节数」四层归因的,用它做基线最合适。要提醒的是它是快照:长连接(比如挂在后台的下载、常驻的 IM 连接)会长期占着一条计数,看一次容易得出错误结论,必须按时间窗口重复采样。

六、把命中率做成一份"体检报告":采样、基线与异常判定

单次采样只是一张照片,连续采样才是体检。做法很简单:每 5 分钟拉一次 /connections,把 (rule, rulePayload) 的计数存进一个按天滚动的文件,一天下来你就有了一份"规则命中画像"。

# 每 5 分钟追加一次采样(cron: */5 * * * *)
CTRL=http://127.0.0.1:9090; SECRET=YOUR_SECRET
curl -s -H "Authorization: Bearer $SECRET" "$CTRL/connections" \
  | jq -r --arg ts "$(date +%s)" '.connections[]
      | "\($ts)\t\(.rule)\t\(.rulePayload)\t\(.chains[-1] // "-")"' \
  >> /tmp/clash-hit-rate.log

有了这份日志,下面这些判断就从"感觉"变成了"数字"。这张表是我建议长期盯的几个指标,以及它们各自的异常含义。

指标正常形态异常形态与含义先查什么
规则类型分布以 Domain 类规则为主,MATCH 只占少数MATCH 占比突然放大 → 前面所有规则都没命中,大概率规则集没加载规则集缓存文件是否存在、内核对规则集是否报错
REJECT 命中量平稳的少量(广告、追踪域名)成倍上升 → 广告规则集被替换成了过度拦截版本,可能误伤正常服务近期是否更新过 reject 类规则集,对比条目数变化
某个策略组命中归零与使用习惯相称长期为 0 → 规则里的 target 名字和策略组名对不上,条目被静默忽略规则集里写的组名与 proxy-groups 的 name 逐字比对
GEOIP 命中占比低位(IP 规则通常只做兜底)明显升高 → IP 类规则排在了域名规则前面,或域名没被嗅探到规则顺序、sniffer 是否开启
直连命中占比符合你的日常比例骤降或骤升 → 国内规则集失效 / 被误配成代理国内域名规则集的更新时间与条目数
命中分布整体漂移缓慢变化一夜之间大幅变化 → 规则集更新引入了行为差异规则集文件的 mtime 与体积变化量

做基线还有一个方法论上的细节:对照出口要足够稳。同样一份规则、同样的使用习惯,如果出口线路本身在抖,你的"命中分布"会跟着漂——连接重试、掉线重连都会污染统计。这也是为什么长期采样建议绑一条抖动量小的线路做对照:像 SS-ID 这种纯 IEPL、线路数量少而稳定的机场,在数周尺度上的表现是一条近乎平的线,任何偏差都更容易被判定为"规则问题";反过来,如果你的对照出口本身波动大,规则与线路两个变量就纠缠在一起了。

还有一个常见误读:连接数不等于流量。一个后台常驻的小连接会让某个策略组的"命中数"看起来很高,但它可能只传了几百 KB。要评估"哪个策略组真的在扛流量",必须把 /connections 里的 upload / download 字段一起聚合,别用连接数下结论。

七、六种典型误分流:现象、根因、修法

命中率数据最大的价值,是把"分流不对"从一个模糊抱怨变成一条可定位的线索。下面是实战中最常见的六种形态,按出现频率排序。

  1. 域名规则完全不命中,全落到 MATCH。最常见的原因是嗅探(sniffer)没开,或者在纯 IP 流量(parse-pure-ip)场景下没有得到域名。内核拿不到域名,所有 Domain 类规则自然无从命中。修法:开启 sniffer 并确认 override-destination 之类的选项与你的场景匹配。
  2. IP 类规则先命中,抢走了本该按域名分流的连接。规则是"先命中先赢",GEOIP、IP-CIDR 这类规则一旦排在域名规则前面,一个本该去流媒体分组的连接会被更早的 IP 规则拦走。修法:把域名规则整体前置,IP 规则只留作兜底。
  3. GEOIP 判定和你的直觉不一致。这里有个容易被忽略的变量:IP 归属库本身。geodata-mode 决定用 .dat 还是 .mmdb,两者的数据来源和更新时点都不同;同一段 IP,尤其是电商/流媒体场景常用的住宅 IP 与原生 IP,在不同库里的归属国家可能不一样,甚至出现"库里查不到这个段"的情况。于是 GEOIP,CN,DIRECT 这样的兜底规则会出现两种反向误判:本该直连的被判成境外走代理,或本该走代理的被判成国内直连。如果你用的机场提供住宅 IP/原生 IP 线路(例如 仙路湾 的住宅 IP 与原生 IP 套餐,定位就是账号环境与流媒体解锁),正确的做法是不要依赖 GEOIP 去猜它的归属,而是在策略组里给它单独安排一条明确的策略,把"IP 归属判断"换成"我知道这条线路是什么"。规则应该服务于确定的事实,而不是替你做推理。
  4. 规则集加载失败,静默回退。规则集拉不到、且本地没有可用缓存时,内核不会让整个配置启动失败,而是让这些 RULE-SET 条目形同不存在,流量继续往后走。现象就是第 1 条:MATCH 占比暴涨、全屋走同一个出口。修法:给关键规则集配 proxy 拉取通道,并保留本地缓存文件。
  5. 策略组名对不上。规则集里写的 target(比如 PROXY、🇭🇰 香港、流媒体)必须与 proxy-groups 里的 name 逐字符一致——包括空格、全角半角、括号和 emoji。差一个空格,这条规则不会报错,只会永远不命中。命中的数据(某个组长期为 0)是发现它的最快路径。
  6. 订阅更新把节点改了名。策略组按名字引用节点,机场换节点命名规则、把某地区节点下线,会导致策略组变空或回落到默认节点。这类问题在命中率上表现为"策略组还在命中,但 chains 里的出口变了"。修法:用"包含关键词"的策略组(url-test / fallback 过滤器)而不是逐节点列举。

排查这类问题时,把"客户端/插件层"和"系统层"分开看会快很多:插件层的典型症状与处置在软路由代理故障排查那篇里有逐条对应;而如果命中数据显示大量域名解析结果异常(同一个域名一会儿直连一会儿代理),那更可能是 DNS 侧的问题,处置方式可以对照软路由 DNS 分流与防污染那篇——DNS 决定了内核看见什么,规则决定它去哪。

八、GEO 数据库:另一个"会变"的部件

很多人把规则集管好了,却忘了 GEOIP / GEOSITE 背后的数据库也是一个需要更新的部件,而且它和规则集的更新机制完全不同。mihomo 侧的相关配置项如下(默认值容易记错,这里按官方文档口径写):

# GEO 数据相关(与 rules 同级)
geodata-mode: true            # true 用 .dat;false 用 .mmdb(默认 false)
geodata-loader: memconservative  # standard 或 memconservative(默认 memconservative,小内存设备用)
geo-auto-update: false        # 默认 false —— 需要显式打开
geo-update-interval: 24       # 单位:小时
geox-url:                     # 自定义下载源,四个数据库分别指定
  geoip: "https://your-mirror.example.com/geoip.dat"
  geosite: "https://your-mirror.example.com/geosite.dat"
  mmdb: "https://your-mirror.example.com/country.mmdb"
  asn: "https://your-mirror.example.com/GeoIP2-ASN.mmdb"

这里有两个值得记住的点。第一,geo-auto-update 默认是关闭的——如果你从没显式打开过它,你的 GEO 数据库可能还是安装那天的版本。第二,OpenClash 自己还有一套 GEO 更新逻辑(有独立的更新脚本、独立锁文件、下载完成校验文件大小后才会替换并重启内核),它的下载地址来自「Github 地址修改」和 GeoIP/GeoSite 自定义 URL 两个入口,而且这两者之间存在优先级问题:当默认的自定义 URL 存在时,某些版本会直接走默认 URL,跳过你在"Github 地址修改"里设置的镜像。如果你在部分地区遇到"GEO 更新一直失败",先确认实际被请求的 URL 是哪一个,而不是反复点"更新"。

实操上的三条建议:把 GEO 更新安排在凌晨的低峰时段(更新成功会重启内核,短暂断流);务必确认 geodata-mode 与策略里用的是同一套数据(用 GEOIP 规则时数据源是哪一份,直接决定了判定结果);小闪存设备上确认落盘位置不是 /tmp,否则每次重启都要重下。

怎么确认"内核实际用的是哪一份 GEO 数据"?两步就能验。第一步看文件:geodata-mode: true 时内核读的是 .dat,false 时读 .mmdb,把工作目录下这两个文件的修改时间列出来,和你在界面里点"更新"的时间对一下,就知道更新到底落在哪一份上。第二步做对照测试:找一条你确定知道归属的 IP(例如你自己 VPS 的落地 IP,或者在境外机器上查到的某个公网地址),在内核日志里访问它,看命中的是 GEOIP 的哪个国家代码。两个结果一致,说明数据与规则配套;不一致,就先怀疑数据版本而不是规则写法。

还有一条容易被忽略的边界:asn 数据库是独立的一份,用到 IP-ASN 类规则时它会单独生效。很多人只更新了 geoip/geosite 两份,却没意识到按 ASN 分流的规则在用另一份老数据——症状是"某类规则的行为和文档描述对不上"。

九、把巡检自动化:一周一次,五分钟看完

运维的终点是不用每天看。下面这份巡检脚本的思路是:把"规则集是否在更新、内核是否在报错、命中分布是否漂移"三件事压缩成一份可读的文本报告,每周跑一次就够(放在路由器上或者任何能访问它的机器上)。

#!/bin/sh
# openclash-ruleset-check.sh —— 每周巡检:规则集 + 命中分布 + 报错
API="http://127.0.0.1:9090"; SECRET="YOUR_SECRET"
DIR="/etc/openclash/rule_provider"
echo "===== 规则集缓存状态($(date '+%F %H:%M'))====="
[ -d "$DIR" ] || echo "!! $DIR 不存在:规则集可能从未落盘"
ls -lh "$DIR" 2>/dev/null | tail -n +2
# 超过 7 天没更新的文件(interval 失效或下载一直失败)
find "$DIR" -type f -mtime +7 -printf '!! 超过7天未更新: %p (%TY-%Tm-%Td)\n' 2>/dev/null

echo "===== 内核报错(近 500 行里的关键词)====="
grep -aiE "rule.?provider|parse|failed|error" /tmp/openclash.log 2>/dev/null | tail -15

echo "===== 命中分布 Top 15 ====="
curl -s -H "Authorization: Bearer $SECRET" "$API/connections" \
  | jq -r '.connections[] | "\(.rule)\(.rulePayload)"' 2>/dev/null \
  | sort | uniq -c | sort -rn | head -15
echo "===== 报告结束 ====="
# crontab:每周一早上 8:05 跑一次,输出留档(按周编号)
5 8 * * 1 /usr/bin/openclash-ruleset-check.sh > /tmp/ruleset-check-$(date +\%Y-\%W).txt 2>&1

这份报告的读法:第一条"规则集缓存状态"里如果有文件被 !! 标记,说明自动更新已经失效,直接去查它的 URL 和 proxy;第二条里的 rule-provider 相关报错是"规则集没加载"的直接证据;第三条的命中分布与前一周对比,出现结构性变化(某个规则类型消失、MATCH 暴涨)就按第七节的顺序排查。

如果把命中画像长期存下来,还可以加一个很轻量的告警:只盯两个数字——MATCH 占比和 REJECT 占比。前者越过阈值说明规则集可能整体失效,后者越过阈值说明拦截规则可能过激。用上面的采样日志做一次 awk 就够了,不需要任何第三方监控系统:

# 从当天采样日志里算 MATCH / REJECT 占比(日志格式:ts\trule\tpayload\tchain)
today=$(date +%Y-%m-%d)
awk -F'\t' -v t="$(date -d "$today" +%s)" '$1 >= t {n++; if ($2=="MATCH") m++; if ($2=="MATCH" || $2=="Rule" && $3~/reject/) r++}
  END {if (n>0) printf "样本 %d  MATCH %.1f%%  疑似拦截 %.1f%%\n", n, m*100/n, r*100/n}' /tmp/clash-hit-rate.log

把这段放进巡检脚本,报告就从"状态描述"升级成了"异常判定"。注意采样文件要按天滚动或定期轮转,否则半年后单文件几十万行,路由器上算一次要等半天。

十、回滚:把"改坏"的窗口压到最短

任何自动化更新都有一次会带回坏东西。运维水平的高低,很大程度上体现在"坏东西进来之后多久恢复"。

  • 保留快照。每次更新前后各存一份 rule_provider 目录的拷贝(带日期目录),体积不大但能救命:cp -a /etc/openclash/rule_provider /etc/openclash/rule_provider.bak-$(date +%F)。保留最近两到三份即可。
  • 记住最小回滚动作。多数故障不需要恢复整份配置:如果是新加的自定义规则引起误判,把那条规则换回注释;如果是某个规则集更新后行为不对,把它的 url 换回上一版固定版本(大多数规则库都有 release 或 tag,可以直接锁定);如果配置本身就乱了,再切回上一份能跑的配置。
  • 回滚也要验证。回滚后重新看一眼命中分布,确认结构回到基线,而不是"点一下重启就当修好了"。

快照的轮转也很简单,保留最近三份就够了(更早的直接删):

# 更新前打快照,只保留最近 3 份
BK=/etc/openclash
cp -a $BK/rule_provider $BK/rule_provider.snap-$(date +%F)
ls -dt $BK/rule_provider.snap-* | tail -n +4 | xargs -r rm -rf

回滚之后不要只看"能不能上网",按下面四条确认回到基线:规则集缓存文件的时间戳与体积是否正常;/connections 里 MATCH 占比是否回落;此前"永远为 0"的策略组是否恢复命中;日志里与 provider 相关的报错是否消失。四条都过,才算真的回滚完成。

十一、决策框架与速查表

你的情况建议的规则集方案
刚上手、设备内存 ≤256MB、只想省心2-3 个社区规则集(代理/直连/广告)+ interval 一天 + proxy 指向稳定出口,不开自建
家里设备多、有明确的分区需求(流媒体/游戏/工作)规则集按用途拆分,每类一个 provider,策略组用关键词过滤而非枚举节点
有内网域名、公司系统、需要人工审核的规则自建托管:规则文件放自己的对象存储或内网 HTTP,用 type: file 或带 token 的 type: http 拉取
小内存设备 + 规则条目上万转 mrs 格式 + geodata-loader: memconservative,但接受"转换要自己托管"的维护成本
跨境电商/多账号/流媒体解锁,对出口 IP 画像敏感不依赖 GEOIP 推测,给专用线路单独建策略组;规则管路径,IP 画像由线路决定
症状先看哪里一句话判断
全屋走同一个出口/connections 里 MATCH 占比MATCH 占比高 = 规则集没加载或规则顺序有误
规则集文件时间戳不动stat + 日志里的 provider 报错下载通道不通,或 proxy 指了个拉不动的出口
某个策略组永远没流量规则里的 target 与 proxy-groups 的 name名字不一致(空格/emoji/全半角)
广告拦截突然变狠或失灵reject 规则集的条目数与更新时间上游规则集换版本了,对比前后条目数
域名分流时对时错DNS 侧与 sniffer 状态内核看到的域名不稳定,先修 DNS/嗅探再看规则
GEOIP 判定反常geodata-mode 与 GEO 数据更新时间数据源不同或版本过旧,别用旧库推新 IP
重启后分流变了缓存目录是否落在 /tmp内存盘被清空,重启后在等重新下载

十二、常见问题(FAQ)

Q1:interval 设成 3600(每小时)是不是更"D新"?
没必要。主流规则库一天更新几个批次,设 86400 或 21600 都够;设太短只会增加上游请求量和路由器负担,还可能因为频繁写盘缩短闪存寿命。真正影响新鲜度的是"能不能拉到",不是"多频繁去问"。

Q2:规则集拉取一定要走代理吗?
不一定。判断标准是:路由器直连能不能在合理时间(比如 10 秒)内拿到那个 URL。能,就用 DIRECT,反正规则集下载只占极小带宽;不能,就指定一个稳定出口。唯一不能做的是"用依赖规则集的策略组去拉规则集"。

Q3:behavior 和文件格式不匹配会怎样?
不会报格式错误那么直白,通常表现为该规则集加载后的条目异常少或为空,规则自然不命中。核对方法:看缓存文件的行数与规则集加载日志。

Q4:命中率统计能不能用流量占比代替?
不能,两者回答不同问题。连接数/命中数告诉你"规则被用到了多少次",流量告诉你"承载了多少数据"。判断"哪条规则在干活"用前者,判断"哪个出口在扛量"用后者(download 字段)。

Q5:为什么刚重启的那几分钟命中分布很难看?
规则集和 GEO 数据在重启后要重新拉取/加载,这段时间里规则是不完整的。重建基线时把重启后 5 分钟的数据排除掉,别让它污染统计。

Q6:规则集条目越多越好吗?
不是。条目越多内存占用越高、加载越慢(小内存设备尤其明显),而且误伤概率上升。宁可少而准:先确保核心的代理/直连/广告三类,再按真实需求增补。内存与性能之间的取舍属于另一个维度,本文不展开。

Q7:自定义规则和规则集同时存在,谁优先?
规则按顺序匹配、先命中先赢,和它来自自定义还是规则集无关。所以自定义规则写在前面就能覆盖规则集,写在后面就只在规则集没命中时生效。位置本身就是优先级。

Q8:怎么确认某个域名到底走了哪条规则?
最快的方式是打开 debug 级日志,用该域名访问一次,在日志里找 "match … using …" 那行——规则类型、匹配值、策略组、节点都在里面。

Q9:规则集更新会不会导致断流?
规则集更新本身一般是热加载,不会断流;但 GEO 数据库更新、订阅配置更新这类操作通常会伴随内核重启,会有几秒到几十秒的断流,尽量安排在低峰时段。

Q10:备份应该包含哪些东西?
至少三样:/etc/openclash/ 下的主配置、rule_provider 与 proxy_provider 目录、以及你自己写的自定义规则文件(在 /etc/openclash/custom/ 下)。前两样是缓存(丢了能重下),第三样是资产,一定要单独存。

Q11:多久巡检一次合适?
规则集更新失败不会立刻让你无法上网,只会让你慢慢跑偏,所以一周一次足够;但如果你刚改过配置或刚换过机场订阅,改完当天一定要看一次命中分布。

Q12:出口线路和规则集运维有关系吗?
有直接关系。规则决定"流量去哪",出口决定"到了之后快不快、稳不稳"。做命中率基线需要稳定对照,日常使用需要出口不掉链子——这两件事都指向同一个结论:出口的稳定性是规则运维的地基,把地基换成抖动的线路,再精细的规则也测不出干净的数据。

Q13:mrs 和 yaml 的规则集能混用吗?
能,同一份配置里的不同 provider 各自声明自己的 format 就行,互不影响。不方便的地方在维护侧:yaml 规则集可以直接用社区现成地址,mrs 通常要你自己转换并托管,所以混用意味着你有两套更新链路(一条跟着上游走,一条跟着自己的转换任务走)。

Q14:改完规则集一定要重启内核吗?
规则集的热更新一般不需要重启;但改动了 proxy-groups、rules 顺序、或者 DNS / GEO 相关配置,就需要让内核重新加载配置。判断标准是"改的是数据还是结构"——数据热更,结构重载。

Q15:怎么确认内核真正加载了多少条规则?
看日志里规则集加载阶段的输出(不同版本会打印加载的 provider 名称与条目数),或者直接看本地缓存文件的行数/字节数是否与上游一致。条目数明显少于上游,就是 behavior 或 format 没配对。

推荐阅读