简介:面向希望在 Windows 环境下体验高性能 HTTP 缓存的开发者与运维人员,这份开源资源提供了一套针对 Cygwin 的 Varnish Cache 修补程序,重点处理文件路径、网络 I/O、线程管理与信号处理等跨环境适配问题,使这款事件驱动、支持 VCL 可编程策略与热升级的缓存服务器能在非标准环境中运行。压缩包共 118 个文件,约 5.54MB,以 36 个 .h 头文件、18 个 .a 静态库、14 个 .exe 可执行程序、13 个 .dll 动态库为主,另含 .1 手册页、.la 库描述、README 与批处理脚本,兼顾编译、运行与查阅。已有 104 人学习。读者可省去完整安装 Cygwin 套件的麻烦,直接使用精简的 cygwin.dll 与 gcc 分发搭建缓存实验环境,验证反向代理、负载均衡与高并发场景,并借助 varnishlog 等工具分析请求、调优缓存策略,理解内存缓存如何降低后端 CPU 与磁盘 I/O 压力,适合具备一定 Linux 基础的读者动手实践。
1. 为什么在 Cygwin 上跑 Varnish Cache:三个真实需求与一条硬边界
Windows 上给 Web 后端加一层 HTTP 缓存,多数团队会直接上 Nginx 的proxy_cache,或者干脆把缓存逻辑塞回应用层。Cygwin 版的 Varnish Cache 走的是另一条路:把这款以 VCL 配置语言和 ban 失效机制著称的开源 HTTP 加速器,架在 Cygwin 提供的 POSIX 兼容层上跑起来。它解决的是「既要 Varnish 的缓存对象模型,又不想为它单独开一台 Linux」这类拧巴需求,也适合手上只有一台 Windows 机器、想读一读 Varnish 内部结构的人。硬边界也摆在这:它不是高并发入口的答案,Cygwin 的进程与共享内存语义决定了吞吐上限。
2. 依赖栈拆解:Cygwin 补了哪些洞,Varnish 又向系统要什么
在动手敲configure之前,得先弄清两边的账本。Varnish 本身是个对系统调用相当挑剔的程序:它要可预期的fork、要能被独立寻址的共享内存段、要精确到毫秒级的时钟、还要一套能在进程间传信号的机制。Cygwin 不是把这些东西原生实现了一遍,它是用cygwin1.dll这层运行时把 Win32 API 包装成 POSIX 语义,包装得好不好,直接决定了 Varnish 能不能跑、跑得稳不稳。这一章把「谁提供什么」拆开讲,看懂了后面的编译报错才知道往哪查。
2.1 Cygwin 的 POSIX 层:进程、信号、共享内存的三个近似
Cygwin 的fork()不是内核级写时复制,而是先CreateProcess再搬运父进程地址空间,代价比 Linux 高一个量级,但语义上仍然给你一个独立子进程。这对 Varnish 很关键,因为它采用 manager/child 双进程模型:manager 负责读命令行、建共享内存、管实例目录,child 负责真正收请求、跑 VCL。child 一旦崩溃,manager 会重新拉起一个,这个「父进程活着、子进程随便死」的容错结构在 Cygwin 下依然成立,只是拉起速度慢一些,崩溃频繁时你在varnishstat上会看到连接数剧烈抖动。
信号是第二个近似点。Varnish 用SIGHUP做某些重载、用SIGTERM做优雅退出,Cygwin 用线程加 APC(异步过程调用)来模拟信号投递,在 mintty 里按 Ctrl+C 和用cygrunsrv拉起服务,信号路径并不完全一样。血泪经验是:交互调试时尽量用-F前台跑,让信号走终端那条路径;做成 Windows 服务后再依赖cygrunsrv的停止逻辑,别指望两者行为一致。
共享内存是第三个、也是最容易翻车的近似点。Varnish 的-s malloc,256m本质是一次mmap(MAP_ANON),Cygwin 会把它映射到 Windows 虚拟内存,功能上没问题。但-s file,/path/to/cache.bin,1g依赖对普通文件的mmap和稀疏文件语义,NTFS 上能不能成、刷新时机对不对,取决于 Cygwin 版本和挂载参数,我一般不在 Windows 上碰 file 后端。
此外还有flock(Cygwin 用LockFileEx模拟)、clock_gettime(精度受 Windows 计时器粒度影响)、/dev/urandom(Cygwin 会桥接到系统随机源)。这些平时感知不到,一旦出问题就是「程序能起来但行为诡异」那种玄学故障。
2.2 编译期依赖与 Cygwin 包名对照
Varnish 的构建依赖分三层:autotools 工具链、C 库开发头文件、以及构建期要执行的 Python 脚本。Cygwin 的包命名和 Debian 系不一样,libfoo-dev在这里叫libfoo-devel,g++叫gcc-g++,第一次配环境的人容易在这一步卡半天。下面这张表是我照着源码包里的configure.ac和README整理出来的对照,装包时按这张表勾。
| Varnish 需要的组件 | 用途 | Cygwin 包名 |
|---|---|---|
| C 编译器 | 编译核心与工具 | gcc-core、gcc-g++ |
| make | 构建驱动 | make |
| autoconf / automake / libtool | 源码包走autogen.sh时必需 | autoconf、automake、libtool |
| pkg-config | 探测库的编译参数 | pkg-config |
| Python 3 | 生成 VMOD 胶水代码等构建脚本 | python3 |
| PCRE2(或 PCRE1) | VCL 里的正则匹配 | libpcre2-devel、libpcre-devel |
| libedit | varnishadm的命令行编辑 | libedit-devel |
| ncurses | varnishstat、varnishtop的字符界面 | libncurses-devel |
| zlib | 部分校验与压缩路径 | zlib-devel |
| OpenSSL | 哈希、可选的 TLS 相关 | libssl-devel |
| bison / flex | 部分语法文件生成 | bison、flex |
| patch | 打移植补丁 | patch、diffutils |
几个选型理由值得单独说。PCRE 那一行,较新的源码分支已经迁到 PCRE2,早一点的还在 PCRE1,两个 devel 包都装上最省事,反正configure会自己挑。ncurses 不是可选项,缺了它varnishstat的 TUI 会被裁掉,只剩-1单次输出。Python 3 一定要装,Varnish 的构建脚本里有.py文件参与代码生成,缺了它make会在中途报一个看起来跟 Python 毫无关系的错。
2.3 运行期依赖:实例目录、secret 文件与日志段
编译装完之后,真正决定能不能跑的是运行期那几样东西。Varnish 用-n指定实例名(也可以给绝对路径),在这个目录下它会生成一组管理文件:_.secret是管理接口的口令,_.vsm_mgt和_.vsm_child是父子进程之间的共享内存映射文件,日志段也挂在同一套 VSM(Varnish Shared Memory)机制上。varnishadm、varnishstat、varnishlog这些工具全都是通过读这些段来跟varnishd通信的,端口只是其中一条路。
这就带来一个 Cygwin 特有的注意点:这些文件的权限由 Cygwin 的 POSIX 权限位映射到 Windows ACL,映射规则写在/etc/fstab的挂载选项里。如果实例目录建在/cygdrive/c/...下,默认挂载往往不带acl选项,varnishd建出来的_.secret权限可能和varnishadm读的时候算出来的不一致,表现就是「服务明明起来了,varnishadm一直说认证失败」。常见做法是把实例目录放在 Cygwin 根文件系统下,比如/var/lib/varnish/<实例名>,绕开盘符挂载那套权限转换。
日志段是另一个隐性子系统。varnishlog读的是共享内存里的环形缓冲区,不是磁盘文件,所以它必须能 attach 到varnishd的 VSM 段上。Cygwin 下如果varnishlog和varnishd不是同一个 Windows 用户跑的,attach 会失败,报错信息还相当含糊。这也是为什么我建议整套东西都用同一个账号跑,别一半用管理员、一半用普通用户。
3. 从 setup 到 make install:一套能照着敲的构建流程
环境账算清楚之后,剩下就是按顺序执行。这一章给出一条可以整段复制的路径:先批量装包,再逐条解释configure的开关,最后处理make阶段的告警和安装后的目录校验。中间任何一步的输出不符合预期,都能在下一章的避坑清单里找到对应条目。
3.1 批量装包:用 setup 的 -P 参数一次装齐
Cygwin 的安装器setup-x86_64.exe支持命令行静默安装,比在图形界面里一个个勾要靠谱得多,尤其是需要重复搭环境的时候。把下面这段存成.bat,改掉镜像地址,用管理员权限跑一遍。
REM 静默安装 Cygwin 到 C:\cygwin64,并一次性装齐构建依赖 setup-x86_64.exe -q -d -N -R C:\cygwin64 ^ -s <your-mirror-url> ^ -P gcc-core,gcc-g++,make,autoconf,automake,libtool,pkg-config ^ -P python3,bison,flex,patch,diffutils,git,wget ^ -P libpcre2-devel,libpcre-devel,libedit-devel,libncurses-devel ^ -P zlib-devel,libssl-devel REM 构建目录单独建一个,避免源码树和产物混在一起 mkdir C:\build\varnish参数逐个说明。-q是无交互模式,不弹向导;-d表示不创建桌面快捷方式;-N只安装、不注册为系统级 setup(避免把安装器本身装进系统目录);-R指定根目录,后面换机器时只要这个路径一致,命令就能复用;-P是包列表,可以重复写多行,逗号分隔,包名写错会直接报错退出,这反而是好事,比图形界面勾错了强。-s后面填你常用的镜像,不同网络环境下速度差别很大,选一个稳定的就行。
装完打开 Cygwin 终端,先验证关键工具在位:
which gcc make autoconf automake libtool pkg-config python3 gcc --version | head -n 1 python3 --version pkg-config --modversion libpcre2-8 libedit ncurses这组命令的意图很直接:which确认可执行文件在PATH里,后面三条确认版本能被正确读出。pkg-config --modversion尤其重要,它不只是看版本,还顺带验证了.pc文件装没装、路径对不对。如果libpcre2-8报Package not found,说明你只装了运行时的libpcre2而没装-devel,回安装器补上。
3.2 configure 关键参数逐条说明
进源码目录,先看有没有autogen.sh。从版本控制直接导出的源码树通常需要先跑一遍生成configure,从发布包解压出来的则已经带好了configure,这一步可以省。判断方法很简单:ls configure有就直接下一步。
cd /cygdrive/c/build/varnish/src ./autogen.sh # 仅当源码树里没有 configure 时才需要autogen.sh内部会调autoreconf -i,所以 autoconf、automake、libtool 三件套必须齐。它跑完会生成configure和一堆Makefile.in,中途如果报possibly undefined macro之类,一般是某个m4文件缺失,回头补pkg-config或libtool。
接下来是核心的configure。我一般用这套参数:
./configure \ --prefix=/opt/varnish \ --sysconfdir=/etc/varnish \ --localstatedir=/var/lib/varnish \ --disable-docs \ --without-jemalloc \ --enable-debugging-symbols--prefix决定装到哪,我刻意避开/usr/local,因为 Cygwin 自己的包管理也往那里写东西,混在一起后卸载很难清干净。--sysconfdir放 VCL 和配置文件,--localstatedir是实例目录的父目录,这两个分开是为了备份策略好做:配置走版本管理,运行时状态不进备份。
--disable-docs是必须加的。文档目录默认要用 Sphinx 构建,Cygwin 上装 Python 文档工具链的性价比极低,关掉之后make能省掉几分钟和一堆无关报错。--without-jemalloc的理由前面提过:Cygwin 没有对应的 jemalloc 包,而且 jemalloc 的内存映射假设在 Windows 虚拟内存管理下未必成立,用 Cygwin 自带的内存分配器更稳。
--enable-debugging-symbols建议开着,尤其是在第一次移植的时候。它会让二进制的体积变大一些,但child崩溃时的栈信息可读性完全不同,排查SIGSEGV这类问题全靠它。
configure跑完,最该看的是末尾那段 summary:它会把 PCRE、libedit、ncurses、zlib 的支持情况逐行列出来。任何一个显示no而你以为装了的,都要回去查pkg-config路径,别急着make。
3.3 make 阶段的告警判读与 install 后的目录校验
make这一步在 Cygwin 上比 Linux 慢不少,一是我前面说的fork开销,二是configure生成的脚本大量调用 shell。并行度别开太高,-j4在四核机器上比较合适,开太大反而会因为进程创建竞争变慢。
make -j4 2>&1 | tee /tmp/varnish-build.log把输出同时存一份到文件里,是因为 Cygwin 终端的回滚缓冲区有限,几百行编译输出刷过去之后想回头找第一条错误很难。tee这个动作看着不起眼,实际能省掉重跑一遍的时间。
编译过程中真正需要停下来看的只有error:和以undefined reference结尾的链接错误。warning:里绝大多数可以放过,但有两类要留意:一是implicit declaration of function,说明某个头文件没包含对,可能在运行期变成段错误;二是cast from pointer to integer of different size,这是 32/64 位假设不一致的信号,Cygwin 是 64 位环境,这类告警值得逐个看。
编译通过后安装:
make install ls -l /opt/varnish/sbin /opt/varnish/bin安装完要校验的不只是文件在不在,还有动态库能不能解析:
ldd /opt/varnish/sbin/varnishd | grep -i "not found"这条命令如果没有任何输出,说明依赖全部能解析。有输出的话,缺的那个库要么是没装 devel 包,要么是装在非标准路径而PATH/LD_LIBRARY_PATH没覆盖到。Cygwin 下还有第三种可能:库文件在,但它依赖的 Windows DLL 不在PATH里,ldd一样会报找不到,这时候要去看/usr/bin下对应的cyg*.dll。
最后确认工具链齐了:
/opt/varnish/sbin/varnishd -V /opt/varnish/bin/varnishstat -h 2>&1 | head -n 20varnishd -V会打印版本和编译时启用的特性,比看configure的 summary 更权威。varnishstat -h能正常输出帮助而不是报共享库错误,说明 ncurses 那条链路也通了。
4. 跑通第一个缓存:VCL 骨架、启动参数与命中判定
装好只是把零件摆上桌,真正验证这套东西能不能用,得让它缓存一个请求并且你能证明它命中了。这一章从一个最小 VCL 写起,讲清楚每条指令在做什么,再落到varnishd的启动参数,最后用varnishstat和varnishlog把「命中」这件事变成可观测的数字,而不是靠猜。
4.1 最小可用 VCL:后端、TTL 与探活
VCL 是 Varnish 的核心,它不是配置文件而是编译型语言,加载时会被编译成 C 再动态链接进 child 进程。下面这份骨架覆盖了日常最常用的四件事:定义后端、做健康探测、在请求阶段决定走缓存还是直穿、在响应阶段决定缓存多久。
vcl 4.1; import std; backend default { .host = "127.0.0.1"; .port = "8080"; .connect_timeout = 2s; .first_byte_timeout = 10s; .between_bytes_timeout = 5s; .probe = { .url = "/healthz"; .timeout = 1s; .interval = 5s; .window = 5; .threshold = 3; } } sub vcl_recv { # 非 GET/HEAD 一律不做缓存,避免把写请求塞进对象存储 if (req.method != "GET" && req.method != "HEAD") { return (pass); } # 带认证头的请求直穿,缓存会泄露权限边界 if (req.http.Authorization) { return (pass); } # 业务用 Cookie 做会话,但不影响资源内容,这里直接摘掉再走缓存 unset req.http.Cookie; return (hash); } sub vcl_backend_response { if (beresp.status >= 500 && beresp.status < 600) { # 后端 5xx 不要缓存太久,只留几秒做请求合并 set beresp.ttl = 5s; set beresp.grace = 10s; return (deliver); } if (bereq.url ~ "\.(css|js|png|jpg|woff2)$") { set beresp.ttl = 1h; } else { set beresp.ttl = 60s; } set beresp.grace = 30s; return (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); }逐段说清楚。vcl 4.1是 VCL 语法版本,必须写在第一行,具体写 4.0 还是 4.1 要看你这份源码包支持的版本,编译不过时先检查这一行。backend块里的三个超时分工不同:connect_timeout是 TCP 建连的上限,first_byte_timeout是从发出请求到收到第一个字节的上限,between_bytes_timeout是响应体两个数据块之间的最大间隔。这三个值设得太松,后端卡住时 Varnish 的工作线程会跟着卡;设得太紧,稍慢的接口就全变成 503,我一般按后端的 P99 再加一点余量。
probe是主动健康检查,它每interval打一次.url,在最近window次探测里失败次数达到threshold就把后端标记为 sick。注意.url指向的路径要足够轻量,别拿业务主接口当探针,否则健康检查本身就成了压力源。
vcl_recv里的三个判断是缓存的边界定义。第一段把非 GET/HEAD 直接pass,第二段把带认证头的请求pass,第三段unset req.http.Cookie是这类缓存最常见的坑:很多框架给每个响应都塞Set-Cookie,如果请求阶段不摘掉 Cookie 头,Varnish 默认会认为这是个性化内容而不缓存,命中率直接归零。摘之前一定要确认你的业务里 Cookie 确实不参与内容生成。
vcl_backend_response决定 TTL。这里按后缀分了两档,静态资源给 1 小时,页面给 60 秒,5xx 只留 5 秒。grace是宽限期,缓存对象过期之后的一段时间内,Varnish 仍然可以先把旧内容发出去、同时后台去后端取新的,这个机制在 Windows 上尤其有用,因为后端可能正在重启。
vcl_deliver里的obj.hits是判断命中最直观的手段,第一次请求必然是 0,之后每次命中都会累加。把这两个响应头做出来,用浏览器开发者工具或curl -I就能直接看。
4.2 varnishd 启动参数与 Cygwin 下的工作目录
有了 VCL,先把varnishd前台跑起来。用-F而不是让它后台化,是因为第一次启动时你想看到所有输出。
/opt/varnish/sbin/varnishd \ -a 127.0.0.1:6081 \ -f /etc/varnish/default.vcl \ -s malloc,256m \ -T 127.0.0.1:6082 \ -n /var/lib/varnish/dev \ -F-a是监听地址,127.0.0.1:6081表示只对本机开放,避免第一次测试就把它暴露到网络上。-f指向 VCL 文件。-s malloc,256m是缓存存储后端,256MB 对测试足够,内存型后端在 Cygwin 上最稳。-T是管理接口,varnishadm通过它下发命令。-n指定实例目录,我给了一个绝对路径,比用实例名更可控,也便于同时跑多个实例做对比测试。
启动之后前台会打印几行关键信息,包括管理的端口、child 的 PID、以及 VCL 编译结果。如果 VCL 有语法错误,它会在这一步直接报出来并退出,不会给你一个「起来了但没生效」的假象。Cygwin 下还要额外确认一件事:实例目录下有没有生成_.secret。
ls -l /var/lib/varnish/dev/ # 期望看到 _.secret、_.vsm_mgt、_.vsm_child 等文件 chmod 600 /var/lib/varnish/dev/_.secret_.secret是管理接口的认证凭据,权限收窄到 600 是常规操作。这一步在 Linux 上通常自动就对了,Cygwin 因为权限映射的关系,偶尔会变成所有人可读,手动收紧一次比较安心。
前端跑通之后,用curl打两个请求验证缓存行为:
curl -sI http://127.0.0.1:6081/index.html | grep -i x-cache curl -sI http://127.0.0.1:6081/index.html | grep -i x-cache第一次返回X-Cache: MISS,第二次应该是HIT,同时X-Cache-Hits递增。如果两次都是 MISS,问题通常出在三处:VCL 里vcl_recv的return走错分支、后端响应带了Cache-Control: no-store之类的头、或者Set-Cookie没被清掉。第二处的排查方法是看varnishlog里后端响应的原始头。
4.3 用 varnishstat 与 varnishlog 判断「到底命没命中」
curl看响应头只能验证单次请求,要看整体命中率得上varnishstat。它在 Cygwin 下有两种模式:不带参数是 TUI 刷新界面,带-1是一次性输出,后者更适合脚本和管道。
/opt/varnish/bin/varnishstat -1 \ -f MAIN.cache_hit \ -f MAIN.cache_miss \ -f MAIN.sess_conn \ -f MAIN.backend_fail \ -f MAIN.n_object-f是字段过滤器,只输出你关心的计数器,不加过滤会刷出上百行。几个字段的含义:MAIN.cache_hit是命中次数,MAIN.cache_miss是未命中次数,MAIN.sess_conn是累计连接数,MAIN.backend_fail是后端失败次数,MAIN.n_object是当前缓存对象数量。命中率的算法是cache_hit / (cache_hit + cache_miss),注意分母不含 pass 的请求,所以这个数字反映的是「可缓存流量里的命中比例」,不会被直穿接口稀释。
varnishlog则是逐请求的详细记录。最常用的过滤是看命中:
/opt/varnish/bin/varnishlog -g request -q "VCL_call eq 'HIT'"-g request表示按请求分组输出,一次请求的所有日志行会聚在一起,读起来比流水账清楚得多。-q是查询表达式,语法接近 VCL 的子集。除了VCL_call eq 'HIT',还有几个我常用的:BereqURL eq "/api/xxx"可以盯住单个接口的缓存行为,RespStatus >= 500用来捞后端错误,VCL_call eq 'MISS'配合-g request能看到一次未命中到底走了哪些 VCL 子程序。
一个实操细节:varnishlog在 Cygwin 的 mintty 下如果出现乱码或刷屏异常,先把TERM设成xterm再试。varnishstat的 TUI 模式对终端能力要求更高,跑不起来就退回-1加watch的组合:
watch -n 2 '/opt/varnish/bin/varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss'watch每两秒重跑一次命令,效果和 TUI 差不多,还不受 terminfo 影响。这套组合我在 Windows 上用得比原版 TUI 还多。
5. 避坑与排查:Cygwin 跑 Varnish 的五类典型翻车
前面几章走的是顺利路径,真实情况是绝大多数时间花在跟报错打交道。这一章按「现象 → 原因 → 解决」的格式,列出我在 Cygwin 上跑 Varnish 遇到频次最高的五类故障,分成构建期和运行期两组。每条都尽量给到可以立刻执行的检查命令,而不是只描述原理。
5.1 构建与安装阶段的三个高频失败
现象一:configure报C compiler cannot create executables,但gcc --version明明正常。
原因是configure要编译一个测试程序并执行它,而这一步在 Cygwin 下依赖临时目录的挂载参数。如果/tmp被设成noexec,或者%TEMP%指向的路径带空格(Windows 用户名里带空格很常见),编译出来的a.exe就跑不起来,configure会把它误判成编译器坏了。查的方法是看config.log末尾那几十行,里面会明确写出「编译成功但执行失败」还是「编译就失败」。解决方式是给configure显式指定一个干净的临时目录:TMPDIR=/var/tmp ./configure ...,并确认/var/tmp没有noexec挂载选项(mount | grep /var/tmp能看出来)。
现象二:make跑到一半报某个.py脚本找不到,或者提示python: command not found。
这是 Python 命名的问题。Cygwin 里装的是python3,可执行文件叫python3.exe,而不少构建脚本的 shebang 写的是#!/usr/bin/env python。源码树里如果保留了这种写法,make在生成代码那一步就会失败。原因是构建系统假设了python这个别名存在。解决办法有两个:装一个python的兼容包,或者在configure之前把解释器名传进去,比如PYTHON=/usr/bin/python3 ./configure ...。我一般选后者,不改系统环境,也不影响别的项目。
现象三:make install成功,但varnishd -V报error while loading shared libraries: cygpcre2-8-0.dll。
现象是二进制在,动态库找不到。原因是configure探测到了 PCRE2 的头文件并链接了它,但运行时 Cygwin 的 DLL 搜索路径没覆盖到那个目录。这里有个容易混淆的点:Cygwin 程序找的是cyg*.dll,不是 Linux 那种.so,它们应该都在/usr/bin或/usr/lib下。先跑ldd /opt/varnish/sbin/varnishd | grep -i "not found"确认缺哪个,再去/usr/bin下ls cygpcre2*看有没有。如果有,说明是PATH的问题,把/usr/bin加进去;如果没有,说明libpcre2-devel没装全,回安装器补。
5.2 运行与缓存阶段的两个隐性故障
现象四:varnishd启动后 child 进程反复退出,varnishstat里的MAIN.sess_conn一直是 0,manager 日志里能看到多次Child start。
这是最典型的启动故障,原因通常有两个。第一个是-s指定的存储后端有问题:-s file,/path/cache.bin,1g在 NTFS 上对稀疏文件和mmap的支持不完整,child 初始化共享内存时失败直接退出。第二个是实例目录权限不对,child 读不到_.vsm_child。区分方法是临时把-s换成-s malloc,64m再启动一次,如果好了就是存储后端的锅;如果还是反复退出,就去看实例目录的权限。解决方式很简单:在 Cygwin 上坚持用-s malloc,并且把-n指向 Cygwin 根文件系统下的路径而不是/cygdrive/下的路径,让权限映射走同一套规则。
# 先停掉现有实例,再用最小配置验证 child 能否稳定存活 /opt/varnish/sbin/varnishd -a 127.0.0.1:6081 -f /etc/varnish/default.vcl \ -s malloc,64m -n /var/lib/varnish/probe -F这段命令的价值在于「最小化变量」:去掉-T、去掉 file 后端、换一个全新的实例目录,能起来就说明原配置里某个参数是元凶。这种二分法比逐条读日志快得多。
现象五:curl -I反复请求,X-Cache一直是 MISS,但varnishstat里cache_miss在涨、cache_hit是 0。
现象是请求确实进了 Varnish,也进了缓存逻辑,但从来没命中过。原因按概率排序有三个:后端响应带了Cache-Control: no-store或private,Varnish 会遵守这些头不做缓存;请求里带了 Cookie 而vcl_recv没摘掉,走了pass;或者vcl_backend_response里的beresp.ttl被设成了 0。排查顺序是从后往前:先用varnishlog -g request -q "BereqURL eq '/index.html'"看这一次请求的完整生命周期,重点看VCL_call那一串是MISS还是PASS,以及BerespHeader里的Cache-Control原样是什么。
# 只看指定 URL 的请求分组,重点确认是 MISS 还是 PASS /opt/varnish/bin/varnishlog -g request -q "BereqURL eq '/index.html'"如果日志里出现VCL_call c recv pass,问题就在vcl_recv的分支判断;如果出现VCL_call c backend_response deliver之后紧跟TTL 0,问题就在后端响应头或beresp.ttl的设置。这条排查路径的关键是先分清「没进缓存」和「进了但立刻过期」,两者改的地方完全不同。
6. 进阶:把命中率与失效操作做成可重复的例行检查
跑通之后,日常运维真正需要的是两件事:一个能定期回答「缓存现在健不健康」的检查,和一套可控的失效手段。前者我习惯做成一段脚本,输出三个数:命中率、后端失败率、当前对象数。
#!/bin/bash # 从 varnishstat 单次输出里算出命中率与后端失败率 STATS=$(/opt/varnish/bin/varnishstat -1 \ -f MAIN.cache_hit -f MAIN.cache_miss \ -f MAIN.backend_fail -f MAIN.backend_conn) get() { echo "$STATS" | awk -v k="$1" '$1==k {print $2}'; } HIT=$(get MAIN.cache_hit) MISS=$(get MAIN.cache_miss) BFAIL=$(get MAIN.backend_fail) BCONN=$(get MAIN.backend_conn) TOTAL=$((HIT + MISS)) if [ "$TOTAL" -eq 0 ]; then echo "no cacheable traffic yet"; exit 0 fi awk -v h="$HIT" -v t="$TOTAL" -v f="$BFAIL" -v c="$BCONN" 'BEGIN { hit_rate = h * 100 / t; fail_rate = (c > 0) ? f * 100 / c : 0; printf "hit_rate=%.2f%% backend_fail=%.2f%%\n", hit_rate, fail_rate; }'这段脚本的逻辑很直白:varnishstat -1输出两列,第一列是字段名,第二列是值,awk按字段名取值再算比率。参数上要注意分母的选取,cache_hit + cache_miss只统计可缓存流量,backend_conn才是后端连接总数。把命中率低于 60% 或后端失败率超过 1% 设成告警阈值,接进你现有的监控通道就行。Cygwin 下可以用 Windows 任务计划调bash.exe -lc来跑,省得再装一套调度。
失效操作用ban,比清空整个缓存精准得多。ban是惰性失效,它记下一条规则,之后每次访问对象时比对,命中规则的就当过期处理,同时有个后台线程负责清理引用。
# 让所有 /static/ 下的对象立即失效 /opt/varnish/bin/varnishadm -T 127.0.0.1:6082 \ -S /var/lib/varnish/dev/_.secret \ ban "req.url ~ '^/static/'" # 只失效某个精确 URL /opt/varnish/bin/varnishadm -T 127.0.0.1:6082 \ -S /var/lib/varnish/dev/_.secret \ ban "req.url == '/index.html'" # 查看当前生效的 ban 列表,确认规则没写错 /opt/varnish/bin/varnishadm -T 127.0.0.1:6082 \ -S /var/lib/varnish/dev/_.secret ban.listban的表达式用的是 VCL 语法子集,支持==和~两种匹配。第三条ban.list是我每次下完 ban 必看的,因为 ban 写错不会报错,只会「看起来生效了其实没匹配到任何对象」,血泪经验是下完立刻ban.list确认规则在列表里,再curl一次看X-Cache是否变成 MISS。
VCL 的变更也别直接改文件重启。正确做法是vcl.load一个新版本、vcl.use切过去:
/opt/varnish/bin/varnishadm -T 127.0.0.1:6082 -S /var/lib/varnish/dev/_.secret \ vcl.load v20240601 /etc/varnish/default.vcl /opt/varnish/bin/varnishadm -T 127.0.0.1:6082 -S /var/lib/varnish/dev/_.secret \ vcl.use v20240601vcl.load会把新配置编译一遍,语法错误在这一步就暴露,老版本继续服务;vcl.use是原子的切换,切过去之后新请求走新配置。万一新版本有问题,vcl.use切回上一个版本名就行,整个过程不重启进程、不断连接。这个习惯是从一次线上事故之后养成的:那次我直接改文件重启,配置里一个拼写错误让服务起了三分钟没起来,而用vcl.load的话,错误在加载阶段就被挡下来了。从那以后我每次改 VCL 都强制走一遍vcl.load到新版本号、vcl.use切换、curl验证X-Cache、再vcl.discard旧版本的流程,一步都不省。希望帮到你。
本文还有配套的精品资源,点击获取