当网站流量逐渐增大,应用服务器疲于应付大量重复的静态与半静态请求时,在前面加一层缓存往往是最立竿见影的优化手段。Varnish Cache 就是专为此而生的一款开源 HTTP 加速器,它把响应内容缓存到内存中,直接在反向代理层完成请求应答,吞吐能力轻松达到传统 Web 应用的十倍以上。本文将从架构原理讲到 VCL 配置语言的实战用法,带你完整入门。

一、Varnish 的架构原理:为什么它这么快
Varnish 的高性能首先来自它的进程模型。它分为管理层(Management Process)和子进程层(Child Process,也叫 worker)。管理层负责读取配置、编译 VCL、启动 CLI 管理接口以及监控子进程;子进程才是真正干活的部分,内部包含若干线程池,每个线程负责接收连接、解析请求、查找缓存并返回响应。由于线程按需创建且支持任务调度,Varnish 可以用较少的资源支撑海量并发连接。
其次是内存策略。Varnish 把缓存对象放在通过 mmap 映射的共享内存区域中,由内核管理换页,避免了用户态频繁 malloc/free 的开销,也天然支持多线程共享访问。配合 modern 操作系统的虚拟内存机制,即使缓存总量超过物理内存,也能借助文件后背存储工作,只是性能会有所下降。
最后值得一提的是 Varnish 的日志系统。它不写传统日志文件,而是把日志写入一块环形共享内存(VSL),需要分析时再由 varnishlog、varnishstat 等工具按需读取。这种设计让日志记录几乎不影响请求处理速度,这也是很多高并发场景选择它的重要原因。
二、请求在 Varnish 中的完整流转过程
理解请求生命周期对写好配置至关重要。一个请求进来后,Varnish 会先解析 HTTP 请求头,然后进入 VCL 状态机。核心状态点有以下几个:
- vcl_recv:请求入口,在这里决定是否查缓存、是否改写请求、是否直接透传给后端。
- vcl_hash:定义缓存键的构成,默认是 URL 加 Host 头。
- vcl_backend_fetch / vcl_backend_response:向后端发起请求以及处理后端返回,决定哪些响应可以被缓存。
- vcl_deliver:把响应交还给客户端前最后的加工点,常用于清理或添加响应头。
- vcl_pipe / vcl_pass:绕过缓存的两种方式,pipe 用于完全不解析后续数据的场景(如 WebSocket 升级),pass 则只是本次不查缓存。
默认情况下,Varnish 只缓存符合 RFC 条件的 GET 和 HEAD 请求,并且会遵循后端返回的 Cache-Control、Set-Cookie 等头信息。如果后端返回了 Set-Cookie,Varnish 默认不会缓存该响应,这一点经常让新手困惑,需要在 vcl_backend_response 中主动 unset 掉才能强制缓存。
三、VCL 语言入门与实战配置
VCL(Varnish Configuration Language)是一种编译为 C 代码的领域专用语言,语法接近 C,每条语句以分号结尾,注释使用 #。VCL 不是过程式语言,而是通过 return() 语句在各个状态点之间流转,例如在 vcl_recv 中写 return(hash) 就表示去查缓存,return(pass) 表示直连后端。
先看一个最小可用的配置示例,包含后端定义和基本缓存控制:
# 后端定义
backend default {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
# 清理客户端可能带来的干扰缓存头
unset req.http.Cookie;
# 静态资源查缓存
if (req.url ~ "\.(css|js|png|jpg|jpeg|gif|ico|woff2?)$") {
unset req.http.Accept-Encoding;
return (hash);
}
# 其他请求透传
return (pass);
}
sub vcl_backend_response {
# 静态资源缓存 1 小时
if (bereq.url ~ "\.(css|js|png|jpg|jpeg|gif|ico|woff2?)$") {
set beresp.ttl = 1h;
unset beresp.http.Set-Cookie;
}
return (deliver);
}
sub vcl_deliver {
# 打个标记,方便调试时判断命中
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
return (deliver);
}配置写好后需要编译加载。Varnish 的配置是热更新的,执行 varnishadm load vcl_name /etc/varnish/default.vcl 编译,再用 varnishadm use vcl_name 切换,整个过程不会中断服务,旧的 VCL 还会保留以便随时回滚,执行 varnishadm vcl.list 可以查看所有版本。
四、缓存清理与健康检查
内容更新后如何让缓存失效是绕不开的话题。轻量级的做法是使用内建的 return(purge) 机制:在 vcl_recv 中匹配 PURGE 请求方法并返回 purge,Varnish 会自动删除匹配哈希的缓存对象,同时建议配合 ACL 限制只有内网可以发起清理请求:
acl purge_allowed {
"localhost";
"192.168.0.0"/24;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge_allowed) {
return (synth(405, "Not allowed"));
}
return (purge);
}
}如果需要按正则批量清理,可以引入 vmod_xkey 或使用 ban 操作,例如 ban('req.url ~ ^/news/'),但 ban 只是在查找时标记过期,不会立即释放内存,大量使用会拖慢查找速度,生产环境要谨慎。
另一个重要配置是后端健康检查(probe)。通过定期探测后端状态,Varnish 能在后端宕机时自动摘除节点,配合多个后端还能实现故障转移:
backend web1 {
.host = "192.168.1.11";
.port = "8080";
.probe = {
.url = "/healthz";
.interval = 5s;
.timeout = 2s;
.window = 5;
.threshold = 3;
}
}
sub vcl_backend_fetch {
# 优先选健康后端
return (fetch);
}五、常见踩坑点
第一,默认监听端口是 6081 而不是 80,上线时别忘了改监听参数或在前端再挂一层负载均衡。第二,Varnish 不支持开箱即用的 HTTPS,通常做法是在前面放 Nginx 做 TLS 终结,把解密后的请求以 PROXY 协议传给 Varnish。第三,X-Forwarded-For 需要在 vcl_recv 中手动追加 client.ip,否则后端拿到的都是代理地址。第四,调试时善用 varnishlog -g request 和 varnishstat,前者能看到每个请求经过的完整 VCL 决策链,后者可以实时观察缓存命中率与线程池负载,是排查性能问题的两把利器。
Varnish CacheVCL配置HTTP加速器修改时间:2026-09-04 20:17:04