Clash / mihomo 调度原理
打开 config.yaml 满屏找 `schedule:` 这个键,你会失望——clash/mihomo 里根本没这个顶层字段。但『每周换订阅』『5 分钟测速优选』『周一凌晨 4 点重发规则集』这些需求,却由三套节奏不同的时钟在悄悄执行:内核 `interval` 按秒,Clash Verge 按分钟,CFW 时代按小时。单位差 60 倍,抄错一个数,就是每 60 秒打一次机场订阅接口。而调度没有独立模块——它是内核 interval、GUI 定时与系统定时器三层各管一段的合谋,搞清每一层谁在计时、单位是什么,才不会把 60 秒当成一小时。
三层时钟:内核、GUI、系统定时器各管一段
调度没有独立模块,散在三层。最底层是内核
proxy-providers/rule-providers 的 interval,单位秒——2020 年 Premium 引入的原生定时拉取,失败用本地 path 缓存兜底;同层 health-check.interval 管节点存活探测。中间是 GUI:CFW(Fndroid)做成小时级内置定时;Clash Verge(zzzgydi,2022-03-04 V2EX 首发)按 profile 独立 update_interval,分钟、默认 1440 每日。最外层是系统定时器:服务器 cron/launchd,GitHub Actions 定时重生成规则集。单位大战:小时、分钟、秒,抄错就 60 倍误差
同一句『更新间隔』,三个实现单位完全不同:CFW 的订阅更新按小时;Clash Verge/Rev 的 profile 填的是分钟,1440 才是每日一次;mihomo 的
interval 按秒,3600 才是每小时。素材里有个真实事故:有人把 proxy-providers.interval 设成 60,以为是一小时,结果等于每分钟打一次机场订阅接口,把机场打爆、token 被风控封禁。另一个坑:health-check 的 url 用境外站点,直连环境全判失败,fallback 组全死、流量降级到 DIRECT。定时更新≠定时生效:拉下来只是文件
interval 到点后,内核只是把新文件拉到本地 path,不代表配置已生效。要生效必须 reload:mihomo 收到 SIGHUP 重读 config.yaml 热替换,在跑连接不中断;systemd 单元的
ExecReload=/bin/kill -HUP $MAINPID 干的就这个。千万别 restart,瞬间断流。服务器范式是先校验再热载:0 4 * * 1 /usr/bin/mihomo -t -d /etc/mihomo && /bin/systemctl reload mihomo。-t 只测不跑,语法错当场报,不至于把服务 reload 挂。测速是测速,切换是切换:tolerance 才是防抖键
url-test 组里的 interval: 300 是『每 5 分钟测一次速』,不是『每 5 分钟切一次节点』。真正决定动不动手的是 tolerance:新节点延迟比当前优,但要优出至少这个毫秒数才切。tolerance: 50 表示只快 30ms 时保持不动,防止网络抖动来回横跳;设 0 则风吹草动就切,忽快忽慢;设太大则坏节点切不走。lazy: true 让组『被用到才测』,省流量;max-failed-times: 5 连续 5 次失败判死。所以这里的定时不是单纯计时器,而是一套『周期采样 + 阈值死区』的健康判定控制器。想按『时间+条件』切节点?自己用 controller API 拼
内核给外部留了后门 external-controller:
curl -X PUT http://127.0.0.1:9090/providers/proxies/airport-a 强制刷订阅;PUT /proxies/Proxy -d '{"name":"SG-01"}' 切 select 组。tankeito 的 clash-verge-auto-switch 即此模式:每 15/30 分钟测速,挑最快节点 PUT 给 controller,launchd 做调度。GUI 只提供傻瓜定时,『早 9 点切日本、晚 11 点切美国』这类条件调度得自己写脚本打 API。实战示例
让『5 分钟测速、50ms 内不切』:
Auto: type: url-test, interval: 300, tolerance: 50, lazy: true。落地再配一条 cron:0 4 * * 1 mihomo -t -d /etc/mihomo && systemctl reload mihomo。子关键词
interval 的单位战争 秒/分钟/小时差 60 倍
CFW 按小时,Verge 的 update_interval 按分钟(1440=每日),mihomo provider interval 按秒。三套单位互相抄极易差 60 倍,把 1440 抄进 mihomo 就是每 24 分钟刷一次。
SIGHUP 热重载 kill -HUP 重读配置
mihomo 收到 SIGHUP 重读 config.yaml 热替换,在跑连接不中断;systemd 的
ExecReload=/bin/kill -HUP $MAINPID 即此。restart 瞬间断流,reload 才是正解。url-test 的防抖语义 interval 采样,差值小不切
url-test 组按 interval 周期测速,新节点延迟比当前优至少 tolerance 毫秒才切换。tolerance: 0 会让抖动触发反复切换,50 是常用防抖值;lazy: true 则组被用到才测,省流量。
失败兜底与旧缓存 拉失败保留本地旧档
provider 拉到一半失败时用本地 path 的旧缓存顶替,所以订阅 token 过期后内核『看起来一切正常』,直到节点全部失效才暴露。要配 health-check 和 max-failed-times 兜底,不能裸等。
controller API 手动触发 PUT /proxies 即时切换
PUT /providers/proxies/airport-a 刷订阅;PUT /proxies/{group} 传 {"name":"SG-01"} 切组。macOS 上要主动优选,就用 launchd 定时调这套接口。
GitHub Actions 重生成规则集 schedule 定时跑脚本推 CDN
社区规则集项目(Loyalsoldier/clash-rules)用 Actions 的 schedule 定时跑脚本生成 .yaml 推 CDN,再被 rule-providers 的 interval 拉走,是『规则集自我维护』的样板。
衍生角度
设计模式:定时拉取 + 缓存兜底
provider 用『定时拉取 + 本地缓存兜底』,失败时复用旧文件不炸主流程;健康检查用 generate_204 小请求做存活探针——和 K8s liveness probe 完全同构。
概念拆解:『更新间隔』其实是三套时钟
CFW 小时 / Verge 分钟 / mihomo 秒,作用对象也各不同(订阅文件、profile、provider)。单位、作用对象、生效时机三个维度别混成一个『定时』。
风格架构:拉配置与跑流量解耦
provider 只管文件落地,reload 才切运行态;GUI 只管把配置翻译成 YAML,调度下放内核与系统定时器。每层职责单一,是典型分层。
原理分析:url-test 不是定时器
它是『周期采样 + 阈值判定』控制器:interval 定采样率,tolerance 定死区。interval 调小不会更快切节点,只会更频繁测速——这是最常被误解的点。
实际用法与坑
- interval 以秒计:3600 每小时、86400 每日;别抄 Verge 的分钟值 1440。
- 先 `mihomo -t` 校验,再 `systemctl reload`;别 `restart`。
- health-check 用 generate_204,境外 url 全判死、流量降 DIRECT。
- 生产 provider interval 别低于 3600 秒,过低会打爆机场接口。
- 『按时间切节点』用 controller API + cron 自己拼,别指望 GUI 傻瓜定时。
下一步动手做
先改一处:把 provider 的 interval 调到 3600,reload 观察拉取;再给 url-test 加 tolerance: 50 看切换。进阶:写 10 行脚本,超 300ms 就 PUT 切组,丢进 launchd。
相关书籍
TCP/IP 详解(卷1:协议)
代理调度本质是 TCP 连接转发与健康探测,读 TCP 连接管理、超时与重传章节,理解 url-test / fallback 的语义。