资讯动态

Windows下用Cygwin构建Varnish验证HTTP缓存与VCL配置

发布时间:2026/10/24 4:42:38 来源:尧图企业网站定制
简介面向 Windows 平台运维人员与 Web 性能优化开发者的 Cygwin Varnish Cache 移植补丁包针对 Varnish Cache 原生依赖 Linux 环境、直接编译运行困难的问题给出了可落地的适配方案。Varnish 以内存缓存 HTTP 对象、VCL 可编程缓存策略、事件驱动高并发与热升级见长作者通过修改路径处理、网络 I/O、线程管理与信号处理等关键代码使其在 Cygwin 中稳定工作并附带精简的 cygwin.dll 与 gcc 分发版免去完整安装 Cygwin 的负担。压缩包共 118 个文件、约 5.54MB以 36 个 C 头文件、18 个静态库、14 个可执行程序与 13 个动态链接库为主体配合 4 份 readme、若干批处理脚本与 vcl 示例配置覆盖编译、运行与调试环节另含 varnishd、varnishlog、varnishtop 等命令手册页便于对照查阅参数与日志字段。已有 104 人学习适合希望在 Windows 侧低成本验证缓存策略、观察请求日志与压测表现的中级开发者参考。1. Windows 上想验证 HTTP 缓存行为为什么我最后选了这个 Cygwin 版 Varnish前端和后端都在 Windows 上跑想在最前面挂一层 HTTP 缓存看看 TTL 过期、Vary、Cookie 剥离这些决策到底会怎么走。Linux 上一条包管理命令就能解决Windows 上没这个待遇。Cygwin Varnish Cache 这份开源资源解决的就是这件事它借助 Cygwin 的 POSIX 层把 Varnish Cache 这套开源软件编译出来让你在 Windows 上跑一个行为跟线上足够接近的缓存实例。它的定位是给“我需要在本机复现一套缓存行为”的人用的包括验证 VCL 写法、对照压测结果、给后端做一层临时缓存生产边缘节点不要指望它原因第 2 章会摊开讲。下面按我自己的构建顺序拆命令能直接抄。2. Cygwin 版 Varnish 的适配边界它到底替你补了哪些系统调用Varnish 不是那种“解压就能双击运行”的绿色软件。它是一个自建事件循环、多进程、多线程、共享内存、运行时编译 C 代码的服务端程序。想在 Windows 上把它跑起来你要么重写一大半平台相关的代码要么让 Cygwin 去承接这些系统调用。这份资源走的是后一条路。搞清楚它承接了什么、没承接什么你才知道后面哪些报错是环境问题、哪些是这套组合先天不足。2.1 Cygwin 的 POSIX 层与 Varnish 的依赖清单Cygwin 的核心是cygwin1.dll它在 Windows 的 Win32 API 之上实现了一层 POSIX 语义。Varnish 用到的几样关键能力Cygwin 覆盖得还算齐fork()、POSIX 线程与读写锁、mmap()的共享映射、dlopen()、poll()、信号、BSD socket。缺的那几样恰好是 Linux 上最“爽”的几样epoll、sendfile、accept4、SO_REUSEPORT以及各种内核零拷贝路径。这带来的第一个直接后果是事件等待器。Varnish 在 Linux 上默认用 epoll在 FreeBSD 上用 kqueueautoconf 探测不到这些就会退到poll等待器。你可以在启动参数里显式钉住它# 明确指定等待器避免 autoconf 探测结果和你的预期不一致 /opt/varnish/sbin/varnishd -p waiterpoll ...poll能工作但每次事件循环都要把 fd 数组整体扫一遍连接数上千以后 CPU 会明显偏在 sys 时间上。这属于这套组合的固有成本不是配置错了。第二个后果是编译期的探测结果。autoconf 在 Cygwin 下会老老实实把sendfile、accept4、SO_REUSEPORT这些判为不可用走功能降级分支所以编译一般能过。真正容易卡住的是依赖库Varnish 必须依赖 PCRE 做正则vcl_recv里的~匹配全靠它CLI 端依赖 libedit 和 ncurses 做行编辑和历史。PCRE 这块要注意分支差异老分支吃的是 pcre1Cygwin 包名libpcre-devel新分支换成了 pcre2libpcre2-devel。别猜直接看源码# 在源码根目录执行比看任何文档都快 grep -rn -i -E pcre2?|libedit configure.ac | head -n 20看到PKG_CHECK_MODULES里写的是libpcre2-8就走 pcre2写的是libpcre就走 pcre1。装错版本的表现是 configure 阶段报Package requirements (libpcre2-8) were not met别硬扛回去换包。2.2 master/child 双进程与 VSM 在 Windows 上的落点Varnish 的进程模型是管理进程加工作进程。varnishd起来之后先当管理进程随后 fork 出一个 child也就是真正的 cache 进程所有请求都由 child 处理。两者之间靠 VSMVarnish Shared Memory通信本质是工作目录下的一个文件双方mmap同一块区域日志、统计、参数都从这块内存里读写。你敲的varnishstat、varnishlog、varnishadm全都是去 mmap 这个文件而不是去连一个 socket。在 Cygwin 下这条链路是通的原因是fork()和文件mmap都在 Cygwin 的能力范围内。但它带来两个 Windows 特有的注意点。一是工作目录必须可写、且路径要干净。默认工作目录是/var/lib/varnish/主机名你可以用-n覆盖。Cygwin 会把 POSIX 路径翻译成 Windows 路径如果你的目录建在C:\Program Files下面翻译过去之后普通权限写不进去child 起不来管理进程只在日志里丢一句创建失败就退了看起来像“闪退”。我一般固定放在/var/lib/varnish/实例名对应C:\cygwin64\var\lib\varnish\实例名不存在权限问题。二是管理进程没有守护进程托管。Linux 上有 systemd进程挂了会被拉起来Cygwin 下没有这层要么你自己开个终端挂着-F前台跑要么用 Cygwin 自带的服务包装器把它注册成 Windows 服务# 把 varnishd 注册成 Windows 服务参数用单引号整体传入 cygrunsrv -I varnish-inst1 -p /opt/varnish/sbin/varnishd \ -a -a 0.0.0.0:6081 -T 127.0.0.1:6082 -f /etc/varnish/default.vcl -s malloc,256m -n /var/lib/varnish/inst1 -j none \ -e CYGWINnodosfilewarning cygrunsrv -S varnish-inst1 # 启动 cygrunsrv -E varnish-inst1 # 停止 cygrunsrv -R varnish-inst1 # 卸载-e CYGWINnodosfilewarning这行是血泪经验不加它Windows 事件日志里会持续刷 POSIX 路径警告把真正的错误淹掉。还有一个容易忽略的点child 进程是要被管理进程重新 exec 起来的也就是说varnishd会以内部参数再启动一次自己。如果你是从cmd.exe或 PowerShell 里手敲命令启动的PATH 里没有 Cygwin 的/bin第二次 exec 找不到cygwin1.dll症状就是进程起来一瞬间就消失。要么在 Cygwin 终端里启动要么把C:\cygwin64\bin加进系统 PATH。2.3 什么时候该用这套组合什么时候别硬上我把这几年的使用场景归了两类列表比任何说明都直观场景建议关键原因本机或内网验证 VCL 写法、缓存策略合适VCL 语法和后端行为与 Linux 版一致改完立刻能验给 Windows 上的后端加一层临时缓存减压合适单机低并发够用配置成本远低于换系统离线环境、不能装 Linux 的机器上做演示合适一套 Cygwin 加一份构建产物就能带走团队只在 Windows 上开发需要复现线上缓存逻辑合适共享同一份 VCL减少“我本地是好的”生产边缘节点、日均千万级请求不建议无 epoll 无 sendfile事件循环是 poll长连接一多就顶到天花板需要 HTTP/2、TLS 卸载、大规模并发不建议这些要么依赖 OpenSSL 的特定路径要么依赖内核能力要求毫秒级 P99 和稳定吞吐不建议Windows 上的定时器和调度精度摆在那一句话总结选型原则把它当“行为验证器”别当“性能组件”。你要验证的是缓存逻辑对不对那它完全够你要验证的是能扛多少 QPS那这个数字在 Windows 上没有参考价值。3. 从零构建一台可跑的实例Cygwin 环境、configure 参数与首次启动这一章是从零开始的完整链路。我假设你手上是一台干净的 Windows 加一个 Cygwin 安装器源码包是这份资源里的压缩包。整个过程大概二十分钟其中编译占大头。3.1 用 setup 一次性装齐依赖别一项一项装装到一半发现缺autoconf再回头是最浪费时间的。直接一次装齐装完重开一个终端让 PATH 生效# -q 静默模式-P 后跟逗号分隔的包名不加空格 ./setup-x86_64.exe -q -P gcc-core,gcc-g,make,automake,autoconf,libtool,pkg-config,python3,libpcre2-devel,libedit-devel,ncurses-devel,dos2unix,patch,gzip,git,cygrunsrv,curl参数说明就三点。-P后面的包名之间不能有空格有空格安装器会当成另一个参数。libpcre2-devel是给新分支准备的如果你的源码是 pcre1 分支换成libpcre-devel两个都装上也无所谓。cygrunsrv只有在你打算把服务注册成 Windows 服务时才必要纯手工调试可以不装。装完先验一遍这一步别省gcc --version | head -n 1 # 期望看到 gcc (GCC) x.y.z pkg-config --modversion libpcre2-8 # 期望输出一个版本号报 not found 说明包装错了 pkg-config --modversion libedit # CLI 依赖缺它 varnishadm 的交互模式会残废 python3 --version # 构建脚本要用版本别太老任何一条报错都别往下走。pkg-config找不到包通常是装了运行库没装-devel包头文件缺失编译到一半才会炸。3.2 源码落地与换行符预处理源码包从 Windows 那边解压或者拷过来十有八九带 CRLF 换行。shell 脚本里混进\r报错信息会非常反直觉比如$\r: command not found新手很容易以为是语法写错了。所以在跑 configure 之前先把构建系统会读到的文件统一转成 LFmkdir -p /usr/src cd /usr/src tar -xf /cygdrive/d/pkgs/varnish-cache.tar.gz cd varnish-cache-*/ # 只处理构建系统会解析的那几类文件全量转换没必要还慢 find . -type f \( -name *.ac -o -name *.am -o -name *.in -o -name *.m4 \ -o -name *.sh -o -name configure -o -name Makefile* \) -print0 \ | xargs -0 dos2unix # 兜底检查还带 CRLF 就别继续 file configure | grep -i crlf echo 还有 CRLF回去再转一遍xargs -0配-print0是为了兼容文件名里带空格的情况dos2unix是幂等的重复跑不会坏事。如果你是从 git 仓库拉的源码而不是压缩包直接设git config core.autocrlf input再 checkout 一次比事后转换干净。源码如果是 git 快照而不是发布包configure脚本可能压根不存在需要自己生成# 只有 configure 不存在时才需要发布包不用跑 autoreconf -fi-fi是强制重新生成并安装辅助脚本。这一步在 Cygwin 上偶尔会因为libtool版本对不上报错遇到就升级libtool再来。3.3 configure 的关键开关与 makeconfigure 我只用三个开关其他的交给自动探测。多写反而容易踩到未测过的组合./configure \ --prefix/opt/varnish \ --sysconfdir/etc/varnish \ --localstatedir/var \ CFLAGS-O2 -g \ 21 | tee configure.log--prefix决定装到哪我固定用/opt/varnish好处是跟系统路径隔开卸载直接删目录。--sysconfdir决定 VCL 和 secret 文件的位置配成/etc/varnish是为了跟 Linux 上的习惯对齐将来把配置搬到线上不用改路径。--localstatedir决定运行时的数据目录也就是 VSM 文件的位置。CFLAGS-O2 -g保留调试符号出问题时 gdb 才看得见东西代价是二进制大一点本机调试完全值得。失败的时候别慌答案只在两个地方# 90% 的构建失败原因在这两处能直接看到 grep -n -i -E error|not found|No package configure.log | head -n 30 tail -n 60 config.logconfig.log才是真相configure.log只是摘要。常见的失败就三类缺-devel包、pcre 版本对不上、C 编译器被 Windows 上的其他工具链比如某个 MinGW抢了 PATH。第三类表现为config.log里出现x86_64-w64-mingw32-gcc那就说明你在错误的终端里执行了命令。configure 过了就编译。-j后面跟核数Cygwin 下nproc能正常返回make -j$(nproc) 21 | tee build.log make install编译期比较常见的警告是setproctitle和pthread_setname_np未声明这两个只影响ps里看到的进程名不影响功能可以无视。如果报的是undefined reference到某个pcre2_*符号那就是 pcre 版本装错了回去第 2.1 节对一遍。装完的目录结构值得看一眼后面配路径要用ls /opt/varnish/sbin # varnishd varnishadm varnishstat varnishlog 等 ls /opt/varnish/share # vmod 的 .so/.dll 和文档share下面的 vmod 是运行时dlopen加载的Cygwin 生成的是.dll后缀。如果你的 VCL 里import了某个 vmod 却报加载失败先来这里确认文件到底叫什么名字。3.4 首次启动-j none、-s malloc 与最小验证先建目录然后把 VCL 单独编译一遍最后才启动进程。这个顺序能帮你把“配置错”和“环境错”分开mkdir -p /var/lib/varnish/inst1 /var/log/varnish /etc/varnish # -C 只编译 VCL 不启动进程语法错会直接指到行号比启动后再看日志快十倍 /opt/varnish/sbin/varnishd -C -f /etc/varnish/default.vcl /dev/null echo VCL OK # 正式启动前台跑方便看管理进程和 child 的输出 /opt/varnish/sbin/varnishd \ -a 0.0.0.0:6081 \ -T 127.0.0.1:6082 \ -f /etc/varnish/default.vcl \ -s malloc,256m \ -n /var/lib/varnish/inst1 \ -j none \ -p default_ttl120s \ -F每个参数都要说清楚因为它们是最容易配错的一层-a是客户端监听地址。写127.0.0.1:6081只有本机能连写0.0.0.0:6081才是所有网卡。第一次启动 Windows 会弹防火墙对话框选允许而且要注意它问的是varnishd.exe这个进程不是端口。-T是管理接口只绑回环别暴露到外网。varnishadm靠它连。-f指向 VCL 文件。-s是存储后端malloc,256m表示在内存里分配 256MB 做缓存这是最简单的选择也最不容易出问题。老版本还有基于文件 mmap 的file存储Cygwin 下也能用但 Windows 的文件锁和杀毒软件扫描会让它表现很不稳定我一般不用。-n是实例工作目录必须提前建好并且可写。多个实例就换不同的-n互不干扰。-j none是这套组合的关键。默认的unixjail 会尝试降权到指定用户Windows 上这套 uid/gid 映射语义对不上child 起不来但报错很含糊。直接关掉降权用当前用户跑代价是安全性降低但本机验证场景完全可以接受。-p是启动时预设参数这里把兜底 TTL 设成 120 秒。启动之后另开一个终端验证。先看端口再看响应头# Cygwin 里没有 ss用 Windows 的 netstat 确认监听 netstat -ano | findstr :6081 curl -sI http://127.0.0.1:6081/ | head -n 20 curl -s -o /dev/null -w code%{http_code} time%{time_total}\n http://127.0.0.1:6081/再敲一下管理口确认 CLI 能通/opt/varnish/sbin/varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 vcl.list /opt/varnish/sbin/varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 backend.list-S指向 secret 文件首次启动时varnishd会在--sysconfdir下面生成。如果这条命令报认证失败去看启动日志里打印的 secret 路径Cygwin 的路径翻译有时候会跟你以为的不一样。4. VCL 落地把默认配置改成能扛住真实流量的一套默认 VCL 只是个能跑的最小样本后端指向 127.0.0.1:8080缓存策略基本等于没有。真要用起来得把后端定义、缓存键、Cookie 处理和静态资源规则都补上。这一节的配置可以直接抄但每段后面的说明要看因为参数取值跟你的后端行为强相关。4.1 VCL 版本声明与内置子程序执行链VCL 是编译型 DSLvarnishd启动或者vcl.load时把它编译成 C再编成共享库dlopen进来。所以 VCL 里的语法错误是启动期错误不是运行期错误这点跟 nginx 的配置很像但它连类型都要检查比 nginx 严格得多。第一行必须是版本声明。写高了版本不支持varnishd会明确告诉你最高支持到哪个版本vcl 4.1;请求进来之后的执行链记住主干就行vcl_recv决定这个请求要不要查缓存返回pass直接过、pipe走裸 TCP、hash进查找然后vcl_hash算缓存键命中走vcl_hit未命中走vcl_miss再进后端后端回来进vcl_backend_response这是唯一能改beresp.ttl的地方最后vcl_deliver把响应交出去这里可以打调试头。最容易搞混的是两个 return 的位置在vcl_recv里写return (lookup)是继续查缓存写return (hash)也是同样效果4.x 里两者等价但语义上hash更明确在vcl_backend_response里写return (deliver)是把这个响应交给客户端并缓存写return (abandon)是我不想要了。4.2 后端定义与超时参数怎么定后端定义里除了 host 和 port真正影响线上表现的是三个超时和一个连接上限backend default { .host 127.0.0.1; .port 8080; .host_header app.internal; // 后端按 Host 分流时必须设 .connect_timeout 1s; // 建连超时内网可以很短 .first_byte_timeout 15s; // 后端首字节慢接口要放宽 .between_bytes_timeout 2s; // 字节间隔防后端半死不活挂着 .max_connections 200; // 到后端的最大连接数 }.connect_timeout设 1 秒是我踩过坑之后的习惯内网建连正常在毫秒级1 秒还没连上说明后端进程已经挂了或者端口错了早点失败让客户端重试比挂着强。.first_byte_timeout才是最需要跟业务对齐的。后端有个报表接口要跑 30 秒你这里设 15 秒缓存层就先返回 503 了而后端还在慢慢算。我一般按最慢的接口再留 50% 余量。.between_bytes_timeout是防半死后端的TCP 连上了第一个字节也回来了但后面每隔 10 秒才吐一点没有这个超时就会一直挂着占线程。.host_header特别容易被忽略。后端如果是虚拟主机部署靠 Host 头分流你不设这个字段缓存层透传的是客户端原始的 Host比如cache.local:6081后端匹配不到虚拟主机返回 404然后你花半天查缓存配置。4.3 缓存键、Cookie 与静态资源的三段处理完整的 VCL 我拆成三段看。第一段在vcl_recv里做分流核心思想是只缓存该缓存的sub vcl_recv { # 只缓存 GET/HEAD其他方法直接放行不管 if (req.method ! GET req.method ! HEAD) { return (pass); } # 带 Authorization 的一律不缓存避免串号 if (req.http.Authorization) { return (pass); } # 静态资源剥掉 Cookie让它能进缓存 if (req.url ~ \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$) { unset req.http.Cookie; return (hash); } # 动态接口一律不缓存交给后端 if (req.url ~ ^/api/) { return (pass); } set req.backend_hint default; return (hash); }这段的关键在顺序先排除非 GET/HEAD再排除带认证的最后才是静态资源剥离 Cookie。顺序反了静态资源分支会先匹配上带 Authorization 的静态请求也被缓存了这就是安全问题了。第二段在vcl_backend_response里决定后端返回的东西能不能缓存、存多久sub vcl_backend_response { # 后端明确说不缓存就尊重它但要留个兜底供调试 if (beresp.http.Cache-Control ~ no-store|private) { set beresp.uncacheable true; set beresp.ttl 0s; return (deliver); } # 带 Set-Cookie 的响应默认不可缓存静态资源例外 if (beresp.http.Set-Cookie) { if (bereq.url ~ \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$) { unset beresp.http.Set-Cookie; } else { set beresp.uncacheable true; return (deliver); } } # 静态资源给一天动态内容沿用后端 TTL没有就用兜底值 if (bereq.url ~ \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$) { set beresp.ttl 1d; } elsif (beresp.ttl 0s) { set beresp.ttl 120s; } # 宽限期TTL 过期后还能拿旧对象顶一段配合后端故障时很有用 set beresp.grace 10m; return (deliver); }beresp.uncacheable true这个赋值必须跟return (deliver)成对出现只设标志不 return后面的逻辑还会继续跑很容易出现明明设了不可缓存但还是进了缓存的玄学现象。第三段在vcl_deliver里打命中标记这是排查阶段最有用的东西上线前别急着删sub vcl_deliver { if (obj.hits 0) { set resp.http.X-Cache HIT; } else { set resp.http.X-Cache MISS; } set resp.http.X-Cache-Hits obj.hits; return (deliver); }obj.hits是这个对象被命中的次数MISS 时是 0。用curl -I连着打两次同一个 URL第二次看到X-Cache: HIT就说明整条链路通了。4.4 用 varnishstat 和 varnishlog 验证命中率配置写完不算完得看数据。varnishstat给的是全局计数器-1表示只输出一次-f过滤你关心的字段# 注意 -n 必须跟启动时的实例目录一致否则看到的是另一个实例的数据 /opt p a hrefhttps://download.csdn.net/download/weixin_42118423/17429358 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑