在现代 Web 后端架构中,数据库与 API 的响应速度直接决定了用户体验。Varnish 作为一款高性能 HTTP 加速器(反向代理缓存服务器),能有效减轻后端负载,提升服务端吞吐量。本文将从原理到实战,带你全面掌握 Varnish 的配置、部署与优化技巧。

什么是 Varnish?

Varnish 是一个专注于 HTTP 缓存的中间件,位于客户端与后端服务器(如 Tomcat、Apache)之间。当用户请求资源时,Varnish 会先检查本地缓存:缓存命中则直接返回,缓存未命中才向后端请求并缓存结果。这种机制大幅减少了后端数据库与 API 的重复计算压力。

生活类比:Varnish 就像商场的“快速提货区”——热门商品提前摆好,顾客直接拿走;后端服务器则是后方仓库,存储所有商品。缓存命中=货物在提货区,缓存未命中=需要去仓库取货。

在这里插入图片描述

Varnish 与其他缓存方案对比

在技术选型时,需根据场景权衡:

  • Varnish:专职 HTTP 缓存,内存管理、哈希与锁针对缓存场景优化,VCL 支持热加载策略。
  • Nginx:全能网关,缓存只是其功能之一,适合一体化部署。
  • Squid:老牌代理,功能全面但配置较重。
  • CDN:边缘节点分发,适合全球加速,源站前可再加 Varnish 做二级缓存。
  • Redis/Memcached:应用层 KV 缓存,适合存储动态数据。
特性VarnishSquidNginx
定位专用缓存代理+缓存反向代理
性能极高中等
配置语言VCL配置文件配置文件
缓存粒度细粒度粗粒度粗粒度
生活类比专职快递员老式综合商店现代购物中心
软件定位缓存能力性能配置/扩展典型场景与 Varnish 选型建议
Varnish专用 HTTP 反向代理缓存极强:VCL、purge/ban、grace、细粒度键极高VCL、热加载站前缓存、API 缓存、减轻后端专职缓存、高并发、可编程策略时首选。
Nginx反向代理 + Web 服务器 + 缓存中等:proxy_cache、proxy_cache_key 等配置文件、Lua 等负载均衡、动静分离、简单缓存已有 Nginx、缓存需求不复杂时用 Nginx 即可;要更强缓存与策略用 Varnish。
Squid正向/反向代理 + 缓存强:ACL、refresh_pattern、purge中等配置文件(较复杂)正向代理、企业上网缓存、传统反向缓存要正向代理或已有 Squid 运维经验时可保留;新项目做反向缓存更推荐 Varnish。
Apache Traffic Server反向代理 + 缓存(Apache 系)强:插件、规则、ESI配置文件、插件大流量 CDN 节点、运营商级缓存与 Varnish 同属「专职缓存」;ATS 偏 CDN 与插件生态,Varnish 偏 VCL 与自建控制。
Caddy自动 HTTPS 的反向代理/Web 服务器弱:无内置 HTTP 缓存Caddyfile、简单自动证书、简单反向代理不做 HTTP 页面缓存;要缓存需前置 Varnish 或 Nginx。
HAProxy负载均衡 + 四层/七层代理无:不缓存响应极高(四层/七层转发)配置文件负载均衡、高可用、SSL 终结只做分流与高可用;缓存层用 Varnish/Nginx 放在 HAProxy 后面。
CDN(Cloudflare/阿里云等)边缘加速 + 安全强:边缘节点、就近命中、防 DDoS依赖节点与回源控制台/API静态资源、全站加速、防攻击就近、免运维、按量付费用 CDN;要自建、数据不出域、策略完全可控用 Varnish。
Redis / Memcached应用层 KV 缓存非 HTTP:键值存储,由应用读写极高(内存)应用代码Session、接口结果、热点数据应用内缓存,不是 HTTP 反向缓存;可与 Varnish 并存:Varnish 缓存页面/接口响应,Redis 存 Session 等。

名词解释:命令与核心概念

掌握以下命令是管理 Varnish 的基础:

命令/工具说明生活类比为什么常用?
varnishdVarnish 守护进程,真正干活的进程仓库的「总控室」:启动后负责接单、查库存、叫仓库取货为什么叫 daemon?后台常驻运行,不依赖终端;所有请求都由它处理。
varnishadm管理接口,通过 CLI 与 varnishd 对话经理用对讲机跟总控室说话:加载新规则、清缓存、看状态为什么用 127.0.0.1:6082?管理口只监听本机,避免被外网误操作或攻击。
varnishstat实时统计计数器(命中、未命中、连接数等)仓库门口的「今日提货/入库」大屏为什么看 HIT/MISS?命中率高说明门口货多、回仓库少,后端压力小、响应快。
varnishlog按请求/会话输出详细日志(可过滤)每笔交易的流水单:谁、要了什么、命中还是去仓库取的为什么调试用?能精确看到某请求走了 lookup/pass/fetch 哪条路径。
varnishncsa把日志转成 NCSA 兼容格式(类似 Apache access_log)把流水单整理成「标准报表」给分析工具用为什么需要?很多监控、ELK 等只认 NCSA 格式,便于统一分析。

核心概念

  • VCL:Varnish Configuration Language,用于定义缓存策略,编译为 C 后执行。
  • Backend:后端服务器定义。
  • Director:负载均衡策略。
  • Hash:缓存键计算逻辑。
  • Ban:按条件批量失效缓存。
  • Purge:精确删除单条缓存。
概念说明生活类比为什么重要?
VCLVarnish Configuration Language,Varnish 专用配置语言仓库的「作业手册」:什么货放门口、什么必须去仓库取、谁可以要求清空某格为什么不用普通配置文件?缓存策略复杂(按 URL、Cookie、头、后端健康),需要条件分支和正则,VCL 专为此设计。
backend后端服务器定义(host、port、探针等)仓库的「供应商/仓库房号」:去哪取货、多久算超时、怎么检查供应商是否在线为什么有 probe?后端挂了若不知道,请求会一直打到死节点;探针定期探测,自动摘除故障后端。
director多个 backend 的负载均衡器(轮询、随机等)多个仓库入口的「调度员」:轮流或随机选一个仓库取货为什么用 director?单后端易成瓶颈;多后端分流并配合 probe 可做高可用。
TTLTime To Live,对象在缓存中的存活时间门口货架的「保质期」:过了就认为过期,下次要就去仓库重新取为什么不能无限大?内容会更新(如新闻、价格),TTL 太长会一直卖旧货。
grace对象过期后仍可被使用的一段时间(常在后端故障时)仓库补货延迟时,「先卖库存里过期的」顶一阵,不直接报错为什么有用?后端挂了或很慢时,返回稍旧的缓存总比 502 好,用户体验更稳。
keep在 TTL 之外多保留一段时间,用于发 304 条件请求刷新过期了但还留着「样品」,用来问后端:这批货有没有新版本?有就更新,没有就继续用样品为什么能省带宽?304 只传头不传体,后端说「没变」就继续用缓存,减少回源流量。
pass不查缓存,直接转发到后端且不缓存响应客户要的是「定制/生鲜」,不放进门口货架,每次都去仓库现取为什么 POST/登录等要 pass?会改状态或带 Cookie,缓存会错;必须实时走后端。
pipe把客户端与后端之间打成「管道」,Varnish 只做 TCP 中转客户和仓库直接「连麦」,管理员只负责接通,不拆包、不缓存为什么 WebSocket 等要用 pipe?非 HTTP 或长连接,Varnish 不解析内容,直接透传更合适。
hash查缓存:先算缓存键再 lookup,命中则交付、未命中则 fetch按「单号」(URL+Host 等)查门口有没有货,有就交付,没有就去仓库取并上架为什么只缓存 GET/HEAD?规范上 GET 幂等、可缓存;POST 等会改数据,不能当同一份货反复卖。
HIT缓存命中,直接从缓存返回门口就有货,直接提走为什么追求高 HIT?命中时无需问后端,延迟低、后端负载小。
MISS缓存未命中,需从后端取并可选缓存门口没有,去仓库取,取完可以放一份到门口为什么有 hit-for-pass?某 URL 被标记为「永远不缓存」时,会记一条「不缓存」结果,避免反复去后端问。
purge用 HTTP PURGE 等方法删除缓存中单个对象(精确 URL)经理说:「把 3 号货架第 2 格清空」——只清这一格为什么只支持单 URL?语义简单、实现简单;精确失效时用。
ban用表达式(如正则)让一批对象在下次被命中时视为失效经理说:「所有 3 号货架上的东西都别卖了」——按规则批量失效为什么 ban 是「下次命中时」?不立刻扫全缓存,避免卡顿;命中时再检查 ban 列表,匹配就不交付、去后端取。
stevedoreVarnish 的存储引擎(如 malloc、file)门口货架用「内存」(malloc) 还是「磁盘文件」(file):内存快但重启丢,文件可持久、容量大为什么有 file?内存贵且有限;大容量缓存用 file 存到 SSD/磁盘,重启后还能保留部分缓存。
对比项含义何时用生活类比
pass vs pipepass:走 HTTP,Varnish 解析并转发,但不缓存;pipe:纯 TCP 隧道,不解析普通 HTTP 但不缓存用 pass;WebSocket、CONNECT 等用 pipepass = 管理员代你去仓库取货并交给你;pipe = 管理员只给你接一条直通仓库的电话线
hash vs passhash:查缓存,命中交付/未命中取后端;pass:不查缓存,直接后端可缓存请求用 hash;登录、POST、管理后台等用 passhash = 先看门口有没有;pass = 一律去仓库,不摆门口
purge vs banpurge:删一个 URL 的缓存;ban:按表达式让一批对象失效(下次命中时生效)精确删一条用 purge;删整站/某目录/某后缀用 banpurge = 清空 1 个格子;ban = 贴告示「这类货一律不准从门口卖」
grace vs keepgrace:过期后还能当「保底」用;keep:过期后多留一段时间,用于发 304 刷新防后端故障用 grace;想省带宽、支持 304 用 keepgrace = 断货时先卖临期品;keep = 留样品方便问供应商「有没有新款」

Varnish 架构与工作流程

理解请求在 Varnish 内部的流转路径至关重要。所有请求首先进入 vcl_recv(入口),根据策略决定:查缓存(lookup)、绕开缓存(pass)或管道直连后端(pipe)。后续步骤由该分支决定。

# /etc/varnish/default.vcl
vcl 4.1;
import std;
# 后端服务器定义(为什么要有 .probe?后端挂了要自动摘除,避免请求一直打向死节点)
backend web1 {
    .host = "192.168.1.20";
    .port = "80";
    .probe = {
        .url = "/";
        .timeout = 2s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }
}
backend web2 {
    .host = "192.168.1.21";
    .port = "80";
    .probe = {
        .url = "/";
        .timeout = 2s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }
}
# 定义 directors
sub vcl_init {
    # 轮询负载均衡
    new web_servers = directors.round_robin();
    web_servers.add_backend(web1);
    web_servers.add_backend(web2);
}
# 请求接收
sub vcl_recv {
    # 设置后端
    set req.backend_hint = web_servers;
    # 只缓存 GET 和 HEAD 请求
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }
    # 不缓存特定路径
    if (req.url ~ "^/admin") {
        return (pass);
    }
    # 移除端口号
    set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
    # 去除 www
    if (req.http.Host ~ "^(www\.).*") {
        set req.http.Host = regsub(req.http.Host, "^www\.", "");
    }
    return (hash);
}
# 缓存命中
sub vcl_hit {
    return (deliver);
}
# 缓存未命中
sub vcl_miss {
    return (fetch);
}
# 从后端取回响应后的处理(Varnish 4.x 为 vcl_backend_response,不再使用 vcl_fetch)
sub vcl_backend_response {
    # 设置缓存时长
    if (beresp.http.Cache-Control ~ "max-age") {
        unset beresp.http.Set-Cookie;
        set beresp.ttl = std.duration(beresp.http.Cache-Control, "max-age");
    }
    # 不缓存有 Set-Cookie 的响应(特殊情况除外)
    if (beresp.http.Set-Cookie) {
        if (req.url ~ "^/api") {
            unset beresp.http.Set-Cookie;
        } else {
            return (deliver);
        }
    }
    return (deliver);
}
# 交付内容
sub vcl_deliver {
    # 添加调试头
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
    return (deliver);
}

VCL 内置子例程(subroutines)覆盖了请求全生命周期:

子例程说明使用场景为什么在这个阶段?
vcl_recv请求接收后第一个入口决定 return(hash)/pass/pipe/purge/synth必须先决定「查不查缓存、走不走管道」,后面才能分支。
vcl_hash计算缓存键(可追加 hash_data)自定义缓存键(如按设备类型区分)键决定「同一请求是否命中同一对象」;默认用 req.url + Host 等。
vcl_hit缓存命中时可改为 pass 或 miss 重取命中后通常直接 deliver,少数场景要强制刷新才改。
vcl_miss缓存未命中时通常 return(fetch),去后端取未命中就必须去后端,fetch 后会进入 vcl_backend_response。
vcl_backend_response从后端拿到响应后设 TTL、grace、keep、是否缓存后端响应在这里才能看到状态码、头、体,决定存不存、存多久。
vcl_backend_error后端错误(超时、5xx)时合成错误页或重试为什么单独?后端挂了时可能想返回友好页或从别的后端重试。
vcl_deliver即将把响应交给客户端前加调试头(如 X-Cache: HIT/MISS)最后一环,可改响应头不改体,便于排查。
vcl_synth使用 synthetic() 生成响应时自定义错误页、重定向不走后端,由 Varnish 直接返回内容。

为什么 VCL 没有循环? 设计为“请求进来→判断→返回动作”的线性流程,避免复杂逻辑拖慢每请求速度;复杂逻辑可放在 VMOD(C 扩展)中实现。

Varnish 配置详解

基础配置示例展示了 VCL 语法规则:使用 ~ 表示正则匹配,!~ 表示不匹配。字符串用双引号,长字符串可用 """ 包含换行。

# 1. 安装 Varnish(以 RHEL/CentOS 为例;Debian/Ubuntu 可用 apt install varnish)
yum install varnish -y
# 2. 开机自启并启动
systemctl enable varnish
systemctl start varnish
# 3. 检查状态(确认 Active: active (running))
systemctl status varnish
规则说明示例为什么?
赋值请求/响应头、超时等都可改,决定后续行为。
取消去掉 Set-Cookie 后响应才敢缓存,否则每用户不同。
正则匹配URL、Host、User-Agent 等常用正则区分策略。
取反匹配排除某类请求(如非静态一律 pass)。
字符串替换 / 归一化 URL(如去端口、去 www)便于缓存键一致。
终止每个子例程必须 return 一个动作,否则用默认行为。

部署实战:安装与启动

生产环境推荐使用包管理器安装,便于升级与 systemd 集成。监听 80 端口作为用户流量入口,管理口 6082 仅绑定 127.0.0.1 防止外网访问。存储方式建议用 file(可持久化、容量大)而非 malloc(重启丢失)。

# /etc/varnish/varnish.params
# Varnish 监听地址(0.0.0.0:80 表示所有网卡 80 端口)
VARNISH_LISTEN_ADDRESS=0.0.0.0:80
# 管理接口(仅本机,用于 varnishadm)
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1:6082
# 存储:malloc 为纯内存;file 为文件(可持久、大容量)
VARNISH_STORAGE="file,/var/lib/varnish/varnish_storage.bin,1G"
# worker 线程数(根据 CPU 与并发调整)
VARNISH_WORKER_THREADS=500
# 日志路径
VARNISH_LOGFILE="/var/log/varnish/varnish.log"
# 1. 重启 Varnish(改 VCL 或 params 后通常需 restart)
systemctl restart varnish
# 2. 查看 Varnish 日志(进程/启动类)
tail -f /var/log/varnish/varnish.log
# 3. 管理 CLI:ping 确认与管理进程连通
varnishadm ping
# 4. 实时统计(HIT/MISS、连接数等)
varnishstat
# 5. 请求级日志(调试用,可按 X-Varnish 等过滤)
varnishlog

热加载技巧:修改 VCL 后可使用 vcl.load + vcl.use 加载并切换新配置,旧连接按旧逻辑跑完,新请求用新逻辑,实现零重启部署。

缓存策略与最佳实践

缓存决策树的核心原则:只缓存 GET/HEAD 请求,因为它们是幂等的;POST/PUT/DELETE 会修改资源状态,应直接转发至后端。

策略说明VCL 示例生活类比
只缓存 GETPOST 等不缓存只把「看货」当可缓存,「下单」每次都去仓库。
忽略 Cookie静态资源忽略 cookie图片/CSS/JS 不按人区分,去掉 Cookie 后同一 URL 共用一个缓存。
不缓存管理管理页面不缓存经理办公室的货不能放门口卖,必须每次进办公室取。
强制刷新PURGE 方法刷新缓存经理说「3 号格清空」立即执行。

提高命中率的方法

  • 增加 TTL,延长缓存有效时间
  • 缓存静态资源(CSS/JS/图片)
  • 忽略动态参数(如 UTM 标记)
  • 使用 grace 模式:后端故障时用旧缓存保底

缓存失效:Purge 与 Ban

内容更新后需要让缓存失效:

  • Purge:精确删除单条 URL,立即生效,需通过 ACL 限制来源。
  • Ban:按条件批量失效(如正则匹配),采用“下次命中时检查”策略,避免全缓存扫描。
维度PurgeBan
作用范围单个 URL(及 Vary 变体)匹配表达式的所有对象
是否支持正则否,精确 URL是,如
生效时机立即从缓存移除对象下次被命中时再判断,匹配则不交付、回源取
典型用法发布新文章后删首页缓存全站换主题后 ban 所有 、或 ban

⚠️ 安全提醒:PURGE 必须加 ACL 限制,否则攻击者可反复 purge 导致缓存雪崩。

acl purge_allowed {
    "localhost";
    "127.0.0.1";
    "192.168.0.0"/24;
}
sub vcl_recv {
    if (req.method == "PURGE") {
        if (!client.ip ~ purge_allowed) {
            return (synth(405, "Method Not Allowed"));
        }
        return (purge);
    }
}
# 清除所有 .png(按 URL 正则)
varnishadm ban "req.url ~ '\.png$'"
# 清除某主机的某路径
varnishadm ban "req.http.host == 'www.example.com' && req.url ~ '^/api/'"
# 查看当前 ban 列表
varnishadm ban.list
import std;
sub vcl_recv {
    if (req.method == "BAN") {
        if (!client.ip ~ purge_allowed) {
            return (synth(403, "Forbidden"));
        }
        if (std.ban("req.http.host == " + req.http.host + " && req.url == " + req.url)) {
            return (synth(200, "Ban added"));
        }
        return (synth(400, std.ban_error()));
    }
}

管理工具与性能监控

varnishadm 支持多份 VCL 同时加载(如 new_config、backup),便于回滚。varnishstat 提供实时监控指标:

常用计数器含义生活类比
MAIN.cache_hit缓存命中次数门口直接提货次数
MAIN.cache_miss缓存未命中次数去仓库取货次数
MAIN.sess_conn接受的客户端连接数接待的客户数
MGT.uptime进程运行时长仓库开门了多久
# 查看与管理进程是否连通
varnishadm ping
# 查看运行参数(线程数、storage 等)
varnishadm param.show
# 加载新 VCL(名字 + 来源:default 表示用 -f 指定的主文件)
varnishadm vcl.load new_config default.vcl
# 切换为刚加载的 VCL(新请求立即用新配置)
varnishadm vcl.use new_config
# 按条件 ban(正则),不是删单条 URL
varnishadm ban req.url "^/static/.*"
# 丢弃未使用的 VCL 副本(释放引用)
varnishadm vcl.discard old_config
# 实时统计(每秒刷新)
varnishstat
# 单次输出后退出(便于脚本采集)
varnishstat -1
# 只看某计数器
varnishstat -1 -f cache_hit -f cache_miss

核心指标:关注 cache_hitcache_miss,命中率 = cache_hit / (cache_hit + cache_miss),通常希望 > 90%。

性能优化:线程池与参数调优

线程池的 min 保证有足够 worker 应对突发流量,max 防止连接爆炸。workspace 调大可处理更大请求/响应头,但会占用更多内存。

# /etc/systemd/system/varnish.service.d/custom.conf
[Service]
# 增加文件描述符限制
LimitNOFILE=131072
# 增加线程数
ExecStart=
ExecStart=/usr/sbin/varnishd \
-a :80 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-s file,/var/lib/varnish/varnish_storage.bin,10G \
-p thread_pool_min=200 \
-p thread_pool_max=4000 \
-p workspace_client=128k \
-p workspace_backend=128k

命中率优化配置

sub vcl_backend_response {
    # 对于图片、CSS、JS,缓存 1 天
    if (bereq.url ~ "\.(jpg|jpeg|png|gif|css|js)$") {
        set beresp.ttl = 86400s;
        unset beresp.http.Set-Cookie;
    }
    # 对于 HTML,缓存 1 小时
    if (bereq.url ~ "\.html$") {
        set beresp.ttl = 3600s;
    }
    # grace 模式:后端故障时使用旧缓存
    set beresp.grace = 3600s;
    return (deliver);
}

命中率每提高一截,后端负载就明显下降——延迟低、带宽省、数据库与 API 压力小。

总结:Varnish 核心要点

Varnish 是专业的 HTTP 缓存服务器,能大幅提升 Web 性能。记住口诀:Varnish 专缓存,性能极高是王道;VCL 语言强,灵活配置没商量;动静要分离,静态资源缓存好;grace 模式妙,后端故障能保底;监控要做好,命中率要盯牢。

资源说明
Varnish Cache 官方文档安装、配置、VCL 参考
VCL 4.1 参考语法、变量、子例程、backend、probe
Purging and banningPurge 与 Ban 的官方说明
varnishd 参数启动参数、storage、线程等
方案适用场景与 Varnish 对比
Nginx proxy_cache已有 Nginx、缓存需求不复杂配置简单、无需单独进程;缓存能力与灵活性不如 Varnish。
Squid传统正向/反向代理 + 缓存功能多、历史久;性能与配置灵活性一般不如 Varnish。
Apache Traffic Server大流量、CDN 节点、插件化同属高性能缓存;ATS 偏 CDN/插件,Varnish 偏 VCL 自建。
Caddy自动 HTTPS、简单反向代理无内置 HTTP 缓存;做缓存需前挂 Varnish 或 Nginx。
HAProxy负载均衡、高可用、SSL 终结不缓存;常与 Varnish 搭配:HAProxy 分流,Varnish 做缓存层。
CDN(Cloudflare、阿里云等)静态资源、全站加速、防 DDoS边缘节点、就近命中;Varnish 自建、可控、无按量费用。
Redis / MemcachedSession、接口结果、热点 KV应用层缓存,非 HTTP 反向缓存;可与 Varnish 并存(Varnish 缓存页面,Redis 存 Session 等)。

想象一个智能仓库管理员,常卖的货物他直接放在门口(缓存),客户来了不用进仓库就能取走。Varnish 就是这个"智能仓库管理员",让 Web 服务器减轻负载,用户访问更快!

最后提醒:Varnish 缓存虽好,但动态内容要谨慎!Session、管理页面、带 Cookie 的个性化内容等绝对不能缓存;否则会出现用户 A 看到用户 B 的数据等严重问题。

[AFFILIATE_SLOT_1]

延伸阅读:官方文档与相近方案对比(见前文)。

[AFFILIATE_SLOT_2] setset req.http.Host = "example.com"unsetunset beresp.http.Set-Cookie~if (req.url ~ "\.jpg$")!~if (req.url !~ "^/static")regsubregsuballregsub(req.url, "\.jpg$", ".png")return(action)return (hash)if (req.method != "GET") return (pass);unset req.http.Cookie;if (req.url ~ "^/admin") return (pass);if (req.method == "PURGE") { return (purge); }req.url ~ "\.png$".cssobj.http.url ~ "/news/"