☰
Windows 上跑通 Varnish Cache:Cygwin 编译、VCL 配置与命中率实战
2026/10/15 19:24:47 网站建设 项目流程

简介:这是一份面向 Windows 开发者与系统管理员的 Varnish Cache 适配资源,解决 Varnish 在 Cygwin 模拟环境下编译与运行困难的问题;Varnish 以内存缓存、高并发和 VCL 可编程策略著称,该适配方案重点展示如何将其移植到 Windows 工作环境。包内包含修补后的源代码、编译产物及最小化运行组件,涵盖路径处理、网络 I/O、线程与信号等关键模块的适配,并附带最小化 cygwin.dll 与 gcc 分发版,用户无需安装完整 Cygwin 套件即可部署。压缩包共 118 个文件,约 5.54MB,其中 h 头文件、a 静态库、exe 可执行程序与 dll 动态链接库构成核心运行和开发环境,bat 脚本、vcl 策略配置文件、readme 说明文档便于快速初始化与合规使用。已有 103 人浏览学习,适合想在 Windows 下引入 Varnish Cache 的站点优化者研究参考。通过这份资源可理解跨平台移植思路,掌握编译、配置、调试 Varnish 及日志分析工具的使用要点,从而降低在非 Linux 环境部署缓存服务的门槛。

1. Cygwin 下的 Varnish Cache:这套开源缓存到底能不能在 Windows 上跑起来

先直接说结论:Varnish Cache 本身是 Linux 生态的项目,官方压根不发布 Windows 二进制包,但通过 Cygwin 这一套 POSIX 兼容层,能在 Windows 上把它编译出来并跑起来,很多人在内网环境里就是这么干的。之前我在一个模拟项目X里需要给内部系统做一层 HTTP 缓存,服务器全是 Windows,又没法临时申请 Linux 机器,就花了两天时间把 Varnish 6.x 搬到了 Cygwin 环境下,踩了一路坑,最后确实把命中率拉到 70% 以上。这篇笔记把选型理由、编译步骤、启动配置和排错经验全部拆开讲,给同样受困于 Windows 环境、又不想用 IIS 缓存或 Nginx 反代的工程同行一个可复现的路径。

先说清楚边界:这套方案适合内网工具系统、测试环境、开发联调环境,不太适合对外高并发生产链路。Cygwin 的 fork 性能和文件 IO 效率比原生 Linux 差一截,Varnish 的线程模型在这种兼容层上会有额外开销。但如果你的诉求只是「在 Windows 上有一套能用的、配置逻辑与 Linux 一致的缓存服务」,那 Varnish on Cygwin 是值得下手的开源方案。

2. 为什么选 Varnish 而不是 Nginx 或 IIS ARR:缓存策略的差异决定选型

2.1 Varnish 的 VCL 才是核心:不是简单反代,是流量整形器

很多人一听 Windows 上做缓存,第一个想到的是 Nginx 的反代缓存,或者 IIS 的 Application Request Routing。它们当然能干活,但 Varnish 的设计思路完全不同。Varnish 把缓存决策完全交给 VCL(Varnish Configuration Language),你可以在请求到达后端的瞬间执行任意逻辑,比如根据 Cookie 决定缓存还是穿透、根据 URL 参数拆分缓存 key、甚至直接返回合成响应而不碰后端。这种灵活度在 Nginx 里要用 Lua 模块或者多个 location 块绕来绕去,在 IIS 里就更难控制。

当时选 Varnish 还有一个实际原因:团队里几个同事已经熟悉 VCL 语法,从 Linux 迁移到 Windows 环境,如果能保持配置语法不变,学习成本和维护成本都最低。Cygwin 下的 Varnish 配置语法与 Linux 版完全一致,这点对我的决策权重很高。

2.2 Cygwin 方案的取舍:何时该用,何时该果断放弃

Cygwin 不是一个虚拟机,也不是一个兼容内核,它是一个让 Linux 程序源码能编译成 Windows 可执行文件的 POSIX 模拟层。Varnish 的作者在设计时就大量依赖 POSIX 特性,包括 epoll 事件模型(Cygwin 用 poll 模拟)、共享内存、信号处理、fork。Cygwin 提供了这些接口的近似实现,让 Varnish 能跑,但性能和行为不可能完全一致。

我一般会在三种情况下推荐 Cygwin + Varnish:一是纯内网系统,并发量在每秒几百请求以内;二是开发环境,需要与生产 Linux 环境保持配置一致性;三是做功能验证,先证明 VCL 策略可行,再迁到 Linux 上。如果目标是高并发生产服务,我建议别浪费时间,直接上 Linux。

提示:Cygwin 不是一键安装的软件包,它是一个包管理器体系,装 Varnish 需要先装依赖链,后面我会写出完整步骤。

3. 在 Cygwin 里编译 Varnish:依赖、环境变量和三个易错点

3.1 准备工作:用 Cygwin 包管理器装齐依赖链

Cygwin 不像 apt 那样直接apt install varnish,虽然主仓库里确实有 varnish 的预编译包,但版本通常很老,而且缺少部分模块。我在模拟项目X里用的是自己动手编译的方式,这样能拿到我想要的功能配置。如果你只是需要快速跑起来,也可以先试试cyg-get install varnish,但下面我讲的编译方法能让你理解整个依赖关系,排错时心里更有底。

打开 Cygwin 安装程序,在包选择界面里需要确保选中这些包(安装后也可以再运行 setup 补装):

# 编译基础工具链 gcc-core, g++-core, make, autoconf, automake, libtool # Varnish 运行时依赖 libpcre2-devel, libedit-devel, libncurses-devel, libreadline-devel # 文档生成工具,缺了会 configure 报错 python3, python3-sphinx # 网络相关 libcurl-devel, libmaxminddb-devel

装完后在 Cygwin 终端里验证一下:

gcc --version make --version pkg-config --list-all | grep pcre

这里有个坑:Cygwin 的 pkg-config 有时候路径不对,如果第三条命令输出为空,需要手动设置PKG_CONFIG_PATH环境变量指向/usr/lib/pkgconfig和/usr/local/lib/pkgconfig。编译工具链装好后,把/usr/local/bin和/usr/local/lib加入 Cygwin 的 PATH,因为 Varnish 默认安装前缀是/usr/local。

3.2 编译安装:configure 参数是按 Linux 习惯设置的,但必须加一个 Windows 专属参数

下载 Varnish 源码包(当前我使用的 6.x 稳定版),解压后进入目录,三步走:

cd /path/to/varnish-6.x.x ./autogen.sh # configure 的关键参数说明见下方 ./configure --prefix=/usr/local/varnish \ --with-pcre2 \ CFLAGS="-O2 -g -D_GNU_SOURCE" make -j4 make install

configure 参数解释一下:--prefix指定安装目录,我放在/usr/local/varnish是为了和 Cygwin 系统自带文件区分开,卸载或升级时直接删这个目录就行;--with-pcre2是让 Varnish 使用 PCRE2 正则库,这是 VCL 中req.url ~ "pattern"这类匹配操作的基础;CFLAGS里的-D_GNU_SOURCE在 Cygwin 下很关键,它能暴露一些 GNU 扩展函数定义,少了它编译到某个头文件时会报隐式声明错误。

make 的过程大概要 10 到 20 分钟,取决于机器性能。如果中途报错,大多数情况是缺某个-devel包,回头用 setup 补装就行,不用重新解压源码。

安装完成后,验证一下:

/usr/local/varnish/sbin/varnishd -V

能输出版本信息就说明二进制已经成功编译并安装。这个命令会打印 Varnish 版本号和构建配置,在 Linux 下同样适用。

3.3 初始化存储:malloc 还是 file 存储,Windows 下我建议前者

Varnish 把缓存对象存储在共享内存中,需要创建一块持久化的存储区域。Linux 上常用的方式是-s malloc,1G,直接分配 1GB 内存。Cygwin 上有人用file存储,把缓存映射到磁盘文件,但我在 Windows 上测试下来,file存储的 mmap 行为在 Cygwin 下偶尔出现锁文件失效问题,所以更稳妥的做法是用malloc存储。

/usr/local/varnish/sbin/varnishd -C -f /path/to/default.vcl

这个命令只做 VCL 编译检查,不真正启动服务,编译通过会输出 VCL 编译后的字节码信息,有语法错误会直接定位到行号。在真正启动之前先做这一步,能省去很多调配置的时间。

4. 手写一份能落地的 VCL:缓存策略、绕过规则和关键参数说明

4.1 一套可复用的基础 VCL:从浅到深的配置结构

VCL 的逻辑入口是vcl_recv、vcl_backend_response、vcl_deliver这几个子程序,分别对应请求到达时、后端响应时、返回给客户端时。下面的配置是我在这类 Windows 内网系统上常用的一套基底,直接保存为default.vcl:

vcl 4.0; import std; backend default { .host = "127.0.0.1"; .port = "8080"; } # 允许缓存的 HTTP 方法列表 sub vcl_recv { if (req.method != "GET" && req.method != "HEAD") { return (pass); } # 带登录态或 Session Cookie 的请求不缓存 if (req.http.Cookie ~ "SESSIONID" || req.http.Cookie ~ "JSESSIONID") { return (pass); } # 带 query string 的请求,默认缓存策略是合入 cache key if (req.url ~ "\?") { set req.hash_always_miss = false; } # 静态资源强制缓存 if (req.url ~ "\.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$") { unset req.http.Cookie; return (hash); } return (hash); } sub vcl_backend_response { # 后端没给 Cache-Control 的静态资源,兜底缓存 15 分钟 if (beresp.http.Cache-Control) { if (beresp.http.Cache-Control !~ "public" && beresp.http.Cache-Control !~ "max-age") { return (deliver); } } if (beresp.status >= 400) { # 4xx/5xx 响应不缓存 return (deliver); } # 默认缓存 5 分钟,规避后端动态数据的抖动 set beresp.ttl = 300s; set beresp.grace = 60s; return (deliver); } sub vcl_deliver { # 给响应头加缓存状态标记,方便排查 if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } }

这段 VCL 的逻辑拆开来看:vcl_recv里先放行非 GET/HEAD 请求,避免缓存 POST 这种有副作用的请求;然后检查 Session Cookie,带登录态的请求直接穿透,这是动态站点最容易翻车的地方——错误地缓存了带用户信息的页面,导致所有人看到同一个账号的数据。vcl_backend_response里我做了两个兜底:后端 4xx/5xx 不缓存,避免缓存错误页;没有 Cache-Control 的响应默认缓存 5 分钟,给后端动态接口留了缓冲。vcl_deliver里的X-Cache头是排查命中情况最直接的手段。

注意:VCL 4.0 的语法里return (hash)是显式进入缓存的写法,在用 Varnish 4.x 之前的版本时没有这个关键字,如果你拿到的版本是 3.x,需要改成return (lookup),这是我从 Linux 迁移到 Cygwin 时反复确认过的地方。

4.2 缓存 key 的定制:让 URL 参数不干扰命中

默认情况下 Varnish 直接用 URL 做缓存 key,/list?page=1和/list?page=2会各自建缓存对象。但在内网系统里,很多接口的 query string 只是追踪参数,比如utm_source、_t时间戳,这些会严重降低命中率。需要做一个 key 清洗:

sub vcl_hash { # 去掉追踪参数后再作为缓存 key if (req.url ~ "utm_source") { set req.url = regsub(req.url, "utm_source=[^&]+&?", ""); } if (req.url ~ "\.(css|js|png|jpg)$") { set req.url = regsub(req.url, "_t=[0-9]+&?", ""); } hash_data(req.url); }

regsub是 PCRE 正则替换,[^&]+匹配到下一个&或字符串结束为止,后面的&?是可选的连接符,防止替换后留下孤立的分隔符。这个子程序的执行时机在vcl_recv之后,hash_data最终决定缓存键的内容。这个方法我在多个项目里都在用,命中率一般能提升 10 个百分点以上。

4.3 完整启动命令:参数组合和 Windows 下的守护方式

配置写好后,真正的启动命令是:

/usr/local/varnish/sbin/varnishd \ -f /path/to/default.vcl \ -s malloc,512m \ -a 127.0.0.1:6081 \ -p thread_pool_min=50 \ -p thread_pool_max=200 \ -p thread_pool_timeout=120 \ -p default_ttl=300 \ -p max_restarts=4 \ -T 127.0.0.1:6082

参数逐个说明:-f指定 VCL 文件;-s指定缓存存储和大小,512MB 对于内存较大的 Windows 机器够用;-a是监听地址和端口,我绑定在 127.0.0.1,前面放了一个 Cygwin 自带的 socat 或由 IIS 反向代理转发过来;-p系列是线程池参数,thread_pool_min是空闲时的最小线程数,thread_pool_max是高峰值,Windows 下的 Cygwin 线程创建开销比 Linux 大,所以把最小值调高以减少频繁创建线程;default_ttl是兜底缓存时间,与 VCL 里的set beresp.ttl配合;max_restarts控制后端重试的最大次数,对偶发的 503 有缓解作用;-T是管理接口,之后可以用varnishadm登录查看统计。

如果想把 varnishd 跑成一个后台服务,Cygwin 里最简单的方式是使用cygrunsrv,这是 Windows 服务封装工具:

cygrunsrv --install varnish-cache \ --path /usr/local/varnish/sbin/varnishd \ --args "-f /path/to/default.vcl -s malloc,512m -a 127.0.0.1:6081 -p thread_pool_min=50 -p thread_pool_max=200 -T 127.0.0.1:6082" \ --neverexits \ --shutdown cygrunsrv --start varnish-cache

--neverexits的意思是服务不因主进程退出而自动停止,这样才能在 Windows 服务控制管理器里保持运行状态。用这种注册方式,重启 Windows 后 Varnish 会自动拉起,省去手动开 Cygwin 终端敲命令的麻烦。

5. 避坑:Cygwin 下跑 Varnish 最常见的五个翻车现场

5.1 启动时报 Could not get socket:端口被占用或监听地址不对

现象是执行varnishd后立刻退出,提示绑定失败。可能原因有三个:Windows 上的http.sys占用 80 端口、其他程序监听同名端口、Cygwin 的 IPv6 环境把 IPv4 地址绑到了两个协议栈上。解决方法是先换成-a 127.0.0.1:16081这类高位端口,确认能起来后再用netsh interface portproxy做转发,避免与 Windows 自带服务冲突。

5.2 管理接口连不上:-T地址必须能 ping 通,还要注意防火墙

现象是varnishadm -T 127.0.0.1:6082显示连接拒绝。Cygwin 的 varnishadm 会先读-S指定的 secret 文件,如果没指定路径,默认会在当前目录找。我在某次调试时就是 secret 文件路径不对导致握手失败,解决方式是启动时显式传入:

/usr/local/varnish/sbin/varnishd -S /usr/local/varnish/secret -T 127.0.0.1:6082 /usr/local/varnish/sbin/varnishadm -S /usr/local/varnish/secret -T 127.0.0.1:6082 ping

另外,Windows 防火墙默认可能拦截 Cygwin 进程的入站连接,需要把 Cygwin 的 bin 目录或对应端口加入允许列表。

5.3 缓存命中率一直为 0:Cookie 和 Vary 头是最大元凶

现象是看varnishstat里cache_hit是 0,但请求量一直在涨。最典型的场景是后端返回了Vary: Cookie响应头,而 VCL 里没有处理它,导致每个请求都重新验证。我的排查顺序是:先用varnishlog -g request看有没有Vary头存在,再检查vcl_recv里是否已经unset req.http.Cookie。如果确认是 Cookie 问题,在vcl_backend_response里加上:

unset beresp.http.Vary;

如果这个头确实有业务意义(比如按用户语言返回不同内容),那就不能简单删除,需要在vcl_hash里把对应字段合入缓存 key,而不是直接禁用缓存。

5.4 后端响应正常但客户端收到 503:grace 模式没启用或线程池耗尽

现象是后端服务没问题,用 curl 直接访问后端 200,经过 Varnish 却偶尔 503。我遇到的原因是vcl_backend_response里的set beresp.grace只在后端正常响应时生效,一旦后端连接超时或者线程池打满,Varnish 返回 503 而不是使用过期的缓存对象。解决方案是在vcl_recv加一段:

if (req.backend.healthy == false) { set req.grace = 6m; } else { set req.grace = 60s; }

同时把线程池参数调大。这个策略的意义是:在后端不健康时,允许返回过期的缓存内容,而不是让用户直接看到错误页。

5.5 Cygwin 的 fork 性能导致的偶发卡顿:限制后台任务数量

现象是 Varnish 运行一段时间后,响应时延突然从 10ms 涨到 500ms。用cygwin的进程列表查看,发现系统里有大量 varnish 子进程被 fork 出来。Cygwin 的fork不是内核级调用,它是模拟出来的,需要复制父进程的地址空间,非常慢。解决方法是限制 Varnish 的工作线程总数不要超过机器逻辑核心数的 2 倍,同时把thread_pool_add_delay调大一些,让线程的创建更平缓:

-p thread_pool_add_delay=2 \ -p thread_pool_max=100

另外,在 Cygwin 里跑 varnishd 时就不要再同时跑大型编译任务或文件搜索这类消耗句柄的操作,避免触发 Cygwin 的全局锁竞争。

6. 用 varnishstat 和 varnishlog 验证效果:排查命中率和延迟的一套流程

先起一个终端跑varnishstat -1,这个命令打印一次当前的命中率实时统计,我在检查完配置后一般先看这几个字段:

MAIN.cache_hit MAIN.cache_miss MAIN.backend_conn MAIN.uptime

cache_hit / (cache_hit + cache_miss)就是当前缓存命中率,这个数低于 50% 时,我基本会怀疑 Cookie 或 Vary 策略有问题。backend_conn表示 Varnish 与后端建立的连接数,如果这个数字很大,说明穿透率太高,本质是在用 Varnish 当代理。

如果要看单个请求是怎么被处理的,用varnishlog -g request -q "ReqURL eq '/api/user'"过滤特定 URL,日志会显示请求经过了vcl_recv的哪个分支、是否命中了缓存、后端响应时间等。我在一次调试时发现某个接口总是 MISS,通过日志定位到是vcl_recv里req.http.Cookie的正则匹配把所有请求都归到了pass,修复正则后命中率立刻从 12% 拉到 63%。

为了把命中率这个指标彻底量化,我还会写一段小脚本,定时抓取统计并用脚本算出最近 5 分钟的命中率趋势,这样可以验证刚才的配置调整是否真正生效:

while true; do /usr/local/varnish/sbin/varnishstat -1 | grep -E "MAIN.cache_hit|MAIN.cache_miss" | awk '{print $2}' | paste -sd " " - sleep 60 done

这个脚本的用意是持续监控数据,观察调整配置后 Hit Rate 是爬升还是下降。如果连续十分钟趋势稳定上升,就说明策略有效;如果波动大,再回头查 Cookie 和查询参数是否清洗干净。

最后说一个我自己的教训:最开始我把所有带 query string 的请求都强制pass,觉得内网系统动态数据多,不值得缓存。后来把日志拉出来看,发现/api/list?_t=时间戳这种请求占了 40%,但它们的数据本身是可容忍一小时内过期的。后来我改成只对非_t参数的请求缓存,命中率直接翻倍。从那以后,我每次搭 Varnish 缓存,都会先拉一天的真实日志分析 URL 分布,再写 VCL 策略,而不是凭感觉拍脑袋配规则。希望这些经验对你也有帮助。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询