🗺️ 缓存全景图:Clash 的六层缓存体系
Clash 之所以能在极低的 CPU 和内存占用下处理数万并发连接,很大程度上归功于它精心设计的多层缓存架构。缓存存在于数据包的每一个处理环节——从 DNS 解析到规则匹配,从连接建立到数据转发——避免了大量重复计算和磁盘 I/O。
本文将按照数据包经过 Clash 内核的先后顺序,逐层拆解六大缓存模块:
- DNS 缓存 —— 域名解析结果的本地存储,包括 Fake-IP 映射表和真实 IP 缓存
- 规则匹配缓存 —— 避免对相同域名反复执行规则树遍历
- 连接复用 —— TCP 连接池与 Keep-Alive 机制,减少握手开销
- UDP NAT 表 —— 为无连接的 UDP 协议维持虚拟会话状态
- 策略组缓存 —— url-test 延迟测试结果的记忆与定时刷新
- Provider 持久化 —— 订阅和规则集的本地磁盘缓存与增量更新
理解这些缓存如何工作,不仅有助于你优化配置,更能在遇到"更新了规则但未生效""内存占用异常增加"等问题时快速定位根源。
🌐 DNS 缓存:Fake-IP 映射表与 TTL 博弈
DNS 解析是代理流程中最容易出现延迟的环节之一。每次连接都需要先将域名转换为 IP 地址,如果每次都向上游 DNS 服务器发起查询,不仅慢,还可能引入隐私风险。Clash 通过两种 DNS 缓存机制来解决这个问题。
Fake-IP 模式的映射表
在 fake-ip 模式下,Clash 为每个域名分配一个虚拟 IP(通常位于 198.18.0.0/16 网段),并在内存中维护一张域名 ↔ 虚拟 IP 的映射表。当浏览器发起连接时,Clash 直接返回虚拟 IP,然后通过映射表反向查找到真实域名,再根据规则决定代理策略。
这张映射表是 Clash 最重要的缓存结构之一。它的优势在于:
- 零延迟响应: 不需要等待上游 DNS 查询,直接返回本地映射结果。
- 隐私保护: 本地网络中的设备只能看到虚拟 IP,无法得知你访问的真实域名。
- 规则精准匹配: 后续的规则匹配基于原始域名而非 IP,因此
DOMAIN-SUFFIX等规则可以正常工作。这与 域名规则匹配 的机制紧密相关。
dns:
enable: true
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
fake-ip-filter:
- '*.lan'
- '*.localdomain'
nameserver:
- 223.5.5.5
- 114.114.114.114然而,Fake-IP 映射表也有风险:如果映射表被清空或重启,所有使用虚拟 IP 的已建立连接会立即中断。这就是为什么 Clash 在 TUN 模式下重启会导致部分连接失效的原因之一。
Redir-Host 模式的真实 IP 缓存
在 redir-host 模式下,Clash 进行真实的 DNS 查询,并将结果缓存在内存中。缓存条目带有 TTL(生存时间),默认遵循 DNS 响应中的 TTL 值。当 TTL 到期后,缓存条目会被清除,下一次请求将重新发起 DNS 查询。
这种模式的优点是兼容性好,不改变 DNS 语义;缺点是延迟更高、隐私性较差。两种 DNS 工作模式的更多细节可参考 TUN 模式 DNS 泄漏章节。
⚡ 规则匹配缓存:域名分流的加速引擎
Clash 的规则引擎需要将每个请求的域名与规则列表逐一比对。当规则数量达到数千条时,线性扫描的代价不可忽视。为此,Clash 内置了一个规则匹配结果缓存。
缓存键与命中条件
规则匹配缓存的键通常是域名 + 协议类型 + 端口的组合。当一个请求到达时,Clash 首先检查缓存中是否已有相同键的匹配结果。如果有,直接使用缓存,跳过整个规则树的遍历;如果没有,则执行完整的规则匹配流程,并将结果写入缓存。
缓存失效的触发条件
规则缓存并非永久有效。以下事件会触发缓存失效:
- 配置文件热重载: 当你通过 API 或信号重新加载配置时,规则缓存会被清空。
- 策略组切换: 手动切换节点或 url-test 自动切换时,相关规则匹配结果可能需要重新计算。
- DNS 结果变化: 在 redir-host 模式下,同一域名解析到不同 IP 时,可能需要重新匹配 IP 类规则。
性能影响
规则缓存对长连接场景(如视频流、WebSocket)的提升尤为明显:一旦一个域名的规则被确定,后续成百上千个数据包都不需要再执行规则匹配。对于短连接密集型负载(如网页浏览),缓存也能减少约 30%-50% 的 CPU 开销。
如果你经常修改规则并希望立即生效,需要注意缓存的存在:有时"规则改了但没效果"的抱怨,就是因为缓存尚未被清空。解决方案是使用 PUT /configs?force=true API 强制刷新。更详细的规则调试方法可参阅 自定义规则编写 中的调试章节。
🔗 连接复用:TCP Keep-Alive 与连接池
对于 TCP 连接,建立连接的三次握手和 TLS 握手是主要的延迟来源。Clash 通过与上游节点的连接复用机制来减少这些开销。
与代理节点的连接池
当 Clash 通过某个代理节点转发流量时,它会尝试复用已经建立的 TCP 连接,而不是为每个请求都创建新的连接。这类似于浏览器的 HTTP Keep-Alive,但在代理层运行。
连接池的大小和空闲超时时间由配置控制:
# Clash Meta 中可配置连接池参数(部分内核支持) profile: store-selected: true store-fake-ip: true # 连接空闲超时(毫秒) keep-alive-interval: 30
对性能的实际影响
在访问大量小文件或 API 请求的场景中,连接复用可以显著降低延迟——避免了重复的 TCP 握手(通常需要 1 个 RTT)和 TLS 握手(需要 2-3 个 RTT)。对于跨国代理场景,这些 RTT 通常高达 100-300ms,复用的收益非常明显。
与 HTTP/3 的交互
值得注意的是,HTTP/3 基于 QUIC 协议,其连接复用机制与 TCP 不同。QUIC 使用连接 ID 而非四元组来标识连接,这给代理层的缓存管理带来了新挑战。具体讨论见 《Clash 与 HTTP/3》。
🔄 UDP NAT 表:无连接协议的会话缓存
UDP 是无连接的,没有像 TCP 那样的握手和拆除过程。但在代理场景中,Clash 需要为每个 UDP "会话"维护状态信息,以便将返回的数据包正确路由回发起请求的应用程序。这个状态表就是 UDP NAT 表。
NAT 表的工作机制
当 Clash 收到一个 UDP 数据包时,它会在 NAT 表中记录:
- 源地址与端口: 发起请求的应用程序
- 目标地址与端口: 要访问的远程服务
- 代理节点信息: 该会话使用的出站连接
- 最后活动时间: 用于超时清理
当返回的 UDP 包到达时,Clash 通过查表找到对应的源地址,将包正确转发回去。没有这张表,UDP 代理根本无法工作。
超时与资源释放
NAT 条目不会永久保留。Clash 会在条目空闲一段时间后自动清理,释放内存。默认超时时间通常为 30 秒到 5 分钟,具体取决于客户端设置。对于游戏和语音通话这类长连接的 UDP 应用,如果超时时间过短,会导致会话中断;设置过长则可能积累大量僵尸条目,浪费内存。
关于 UDP 代理的更多细节,包括 TPROXY 和 TUN 模式下的不同处理方式,请参阅 《TCP 与 UDP 深度对比》。
🧪 策略组缓存:延迟测试结果与记忆机制
策略组中的 url-test 类型会定期测试所有候选节点的延迟,并选择最低的那个。这个过程会产生网络请求,如果每次都全量测试,会浪费大量带宽和 CPU。因此,策略组的测试结果会被缓存并定时刷新。
interval 参数:缓存刷新周期
proxy-groups:
- name: "自动低延迟"
type: url-test
proxies: [节点A, 节点B, 节点C]
url: 'http://www.gstatic.com/generate_204'
interval: 300 # 每 300 秒重新测试一次这里 interval 就是缓存刷新周期:在两次测试之间,策略组会继续使用上一次的测试结果,不会重新发起延迟测试。这本质上就是一种定时失效的缓存机制。
手动切换 vs 自动更新
当用户手动切换策略组中的节点时,缓存结果会被立即覆盖。但如果之后网络状况变化(例如某个节点变得不可用),策略组不会立即感知,而是在下一个 interval 周期到来时通过测试发现。这就是为什么在节点故障后,用户可能会经历一段时间的连接失败,直到自动测试发现并切换。
对于 fallback 策略组,缓存行为略有不同:当当前节点连接失败时,它会立即尝试下一个节点,而不必等待 interval 周期。更详细的策略组调度逻辑请参阅 《策略组完全解析》。
💾 Provider 持久化:磁盘缓存与增量更新
proxy-providers 和 rule-providers 允许 Clash 从远程 URL 加载节点列表或规则集。为了避免每次启动都重新下载,Clash 会将 provider 内容缓存到本地磁盘文件中。
path 字段指定的缓存位置
proxy-providers:
airport1:
type: http
url: "https://your-airport.com/sub?token=xxx"
path: ./profiles/proxies/airport1.yaml # 本地缓存文件
health-check:
enable: true
interval: 300当 Clash 启动时,如果本地缓存文件存在且未过期,它会直接使用缓存,避免立即发起网络请求。这大大加快了冷启动速度——尤其是在网络环境较差或订阅服务器响应缓慢时。
更新间隔与触发条件
Provider 的更新由 interval 控制(单位:秒)。在更新时,Clash 会下载最新内容并覆盖本地缓存文件。如果下载失败,它会继续使用旧缓存,确保服务不中断。这种"降级优先"的策略是 Clash 设计哲学的重要体现。
如果你遇到"更新了机场订阅但客户端仍然显示旧节点"的问题,很可能是因为:
- 本地缓存尚未过期,
interval设置太长。 - 下载失败了,Clash 自动回退到旧缓存。
手动触发更新的方法是调用 PUT /providers/proxies/airport1 API,或在客户端界面点击"更新"。如果仍然失败,可以参考 《订阅失败终极排查手册》。
⏳ 失效策略:TTL、LRU 与主动刷新
缓存不可能无限增长。Clash 使用了多种失效策略来在内存占用与缓存命中率之间取得平衡:
| 缓存类型 | 失效策略 | 典型参数 |
|---|---|---|
| DNS Fake-IP 映射表 | 按需创建,连接关闭后保留至过期 | fake-ip-filter 排除列表 |
| DNS 真实 IP 缓存 | TTL 到期自动清除 | 由上游 DNS 响应决定 |
| 规则匹配缓存 | 配置重载时清空;LRU 淘汰 | 固定大小,最近最少使用淘汰 |
| TCP 连接池 | 空闲超时关闭 | keep-alive-interval |
| UDP NAT 表 | 空闲超时清除 | 30 秒 - 5 分钟 |
| 策略组延迟缓存 | 定时刷新(interval) | 通常 120-600 秒 |
| Provider 磁盘缓存 | 定时更新覆盖 | interval 秒,失败时保留旧缓存 |
值得注意的是,Clash 的规则匹配缓存使用 LRU(最近最少使用) 算法。当缓存达到容量上限时,最长时间未被访问的条目会被优先淘汰。这种策略非常适合网络流量模式——热门域名(如 Google、YouTube)的访问频率远高于冷门域名,LRU 能自然地保留高频条目的缓存。
在内存敏感的设备上(如老旧路由器或小型 VPS),你可能需要手动缩减某些缓存的大小。具体配置方法见下节。
🔧 实战调优:内存敏感场景的推荐配置
场景一:树莓派或低内存路由器
这类设备通常只有 512MB 到 1GB 内存。建议采取以下措施减小缓存开销:
- 使用
redir-host而非fake-ip,避免维护庞大的映射表(虽然会牺牲一些延迟)。 - 缩短 UDP NAT 超时时间,例如设置为 30 秒。
- 减少策略组的
interval(如 600 秒),降低测试频率和内存中的状态数量。 - 精简规则数量,使用
GEOIP,CN,DIRECT替代大量域名规则。参见 大型配置文件管理。
场景二:高并发下载服务器
这类场景更注重吞吐量而非内存占用。建议:
- 保持
fake-ip模式以减少 DNS 延迟。 - 增大 TCP 连接池大小和 Keep-Alive 时间,充分利用连接复用。
- 适当延长 UDP NAT 超时,避免长时间下载的流媒体会话中断。
场景三:频繁切换节点的用户
如果你经常手动切换策略组,可以:
- 将 url-test 的
interval设短一些(如 120 秒),让延迟数据更新更及时。 - 使用
fallback策略组而非select,自动在节点故障时切换,减少手动干预。参见 策略组指南。
🛠️ 缓存相关故障排查
规则匹配缓存可能没有刷新。尝试通过 API 强制重载配置(
PUT /configs?force=true),或在客户端界面点击"重载配置"。如果仍然无效,检查规则顺序是否正确。相关调试方法见 域名匹配规则 中的调试章节。
Provider 本地缓存可能尚未过期,或者更新失败后自动回退了旧缓存。检查日志中是否有
provider update failed 错误。手动触发更新并观察日志。
Fake-IP 映射表或 UDP NAT 表可能存在泄漏。检查是否有异常数量的长连接,或者将日志级别设为
info 以减少日志缓冲。重启客户端通常会释放所有缓存。
这是 Fake-IP 映射表被清空后的正常现象。旧连接仍然指向不存在的虚拟 IP,只能等待应用超时后重连。如果频繁需要重启,考虑使用
redir-host 模式。
📌 总结:缓存即性能
Clash 的缓存系统是它能够在低资源设备上运行复杂代理规则的基石。从 DNS 映射表到 UDP NAT 表,从规则匹配加速到 Provider 磁盘持久化,每一层缓存都经过精心设计,兼顾了性能、内存和一致性。
理解这些缓存机制,不仅能帮助你解决"规则不生效""节点不更新"等日常困惑,更能让你根据实际场景进行精细调优。如果你想进一步掌握 Clash 的内部架构,推荐阅读 《透视 Clash for Windows 内部》 和 《大型配置文件管理指南》。
缓存是代理性能的隐形引擎——它不直接体现在配置文件中,却在每一次数据包的流转中发挥着关键作用。