看到 nginx 1.29.6 发布消息的时候,我第一反应是去翻 changelog。这次版本号跳得挺快,而且“新增上游粘性会话支持”这几个字,对做 Web 集群的人来说实在太扎眼了。nginx 的 upstream 负载均衡用了这么多年,会话保持一直是块心病:ip_hash 不够灵活,第三方 sticky 模块又怕不兼容,现在主线版终于把这块短板补上了,而且顺带还有一堆性能和稳定性优化。
作为一个从 nginx 0.8 时代就开始折腾的老用户,我这些年从单机转发到多节点集群,从裸机部署到容器编排,踩过不少和 upstream、会话保持相关的坑。这篇博文我会从版本定位、粘性会话原理与配置、性能提升细节、升级实操几个角度来拆,适合正在用 nginx 做反向代理和负载均衡、想了解 1.29.6 到底改了啥、以及准备平滑升级的运维和开发同学。如果你的架构里刚好有登录态保持、购物车会话这类强状态需求,这次更新值得你花几分钟看完。
1. 版本定位:1.29.6 在 nginx 更新序列里到底是什么段位
1.1 主线版和稳定版,别搞混
很多刚接触 nginx 的朋友,打开官网看到两个下载入口就懵了:一个写着 Mainline version,一个写着 Stable version。简单说,nginx 的迭代节奏是“奇数版本做功能,偶数版本做稳定”。主线版(mainline)是新功能的主战场,功能开发、试验、调优都在这里发生,更新频率快,可能一个月出好几个小版本;稳定版(stable)则是从主线版里挑功能相对完整、bug 相对收敛的版本分支出来的,更新频率低,适合保守的生产环境。
1.29.6 就属于主线版,而当前稳定版还在 1.28 系列。这意味着 1.29.6 里那些新特性,比如粘性会话,是“先头部队”已经验证过的成果,但它本质还是主线版的血统,不是慢工出细活的稳定版。所以选择逻辑很明确:追求新功能、能接受一定风险,就上主线版;追求极致稳定、不急着用新功能,就老实等稳定版。
1.2 1.29 系列走到 6 个版本意味着什么
一个主线系列从 1.29.0 走到 1.29.6,说明功能窗口基本过去了,开始进入 bug 修复和稳定化收尾阶段。这类版本发布的新特性,一般不是“实验玩法”,而是经过内部测试、逻辑相对成熟的功能。粘性会话选在这个节点“上车”,对用户而言是个加分项——至少不用像某些试验特性那样,上线前还要提心吊胆地测半天。
从 changelog 的节奏看,1.29.6 除了新增功能,还收敛了一批此前版本暴露出来的边界情况问题。这种“功能加量 + 稳定性修复”的组合,是比较适合测试环境提前跟进的版本类型。我个人建议:公司内部的预发、灰度环境可以尽快升到 1.29.6,让新功能在真实流量下跑一段时间,等下一两个小版本出来,再评估生产环境是否跟进。
1.3 不同场景下的升级选择建议
给不同背景的读者一个可以直接抄作业的决策表:
| 场景 | 建议 |
|---|---|
| 生产环境,业务刚需粘性会话 | 先在灰度环境完整测试 1.29.6,重点验证 cookie 会话保持和原有业务兼容性,再逐步灰度升级 |
| 生产环境,只用反向代理和普通负载均衡 | 可以继续用稳定版,1.29.6 的其他改进不急于上线 |
| 测试/开发环境 | 直接升 1.29.6,新功能和性能优化都可以提前摸底 |
| 容器/K8s 环境 | 找官方 nginx 镜像对应的 1.29.6 tag,先跑一轮压力测试确认无异常 |
这个决策表的价值在于,不是每个环境都适合无脑升。我把生产环境稳字放在第一位,但也不是一味反对升级——关键是升级前的验证要够,尤其是 1.29.6 这种主线版,新功能越亮眼越要谨慎对待。
2. 粘性会话:这次更新最硬核的新功能
2.1 什么是粘性会话,为什么集群里总绕不开它
粘性会话(sticky session),也叫会话保持,指的是负载均衡器把同一个用户的请求,始终转发到同一个后端服务器。为什么需要这个东西?因为很多老业务、尤其是传统的 Java Web、PHP 应用,会把用户状态(登录信息、购物车、验证码、临时 token 等)直接存在应用服务器的本地内存里。集群部署之后,如果 nginx 用默认的轮询方式分发请求,用户第一次请求打到 A 机器,第二次请求打到 B 机器,B 机器内存里根本没有这个用户的会话数据,用户瞬间就被“踢下线”。
举一个真实场景:一个跑在 Tomcat 上的老旧后台系统,代码里到处是 session.setAttribute,重构量非常大。你不可能为了上集群把所有 session 都迁移到 Redis,这时候最务实的方案就是让 nginx 把同一个用户的流量“粘”在同一台服务器上。粘性会话就是干这个的。
2.2 1.29.6 之前,我们是怎么实现会话保持的
在 nginx 开源版支持粘性会话之前,实现会话保持的思路大致有这么几种:
- ip_hash:按客户端 IP 做哈希,同一个 IP 永远落到同一台后端服务器。缺点很明显:客户端 IP 一变就失效;某些大出口 IP 后面可能挂着成千上万个真实用户,造成某台机器负载特别高;经过 CDN 或四级代理后,客户端 IP 基本失真。
- hash 指令:可以自定义 key,比如按某个请求参数做哈希。灵活了一些,但没法做到“会话级”粘性,同一个用户的不同请求,只要 key 不同,就可能被分发到不同机器。
- 第三方模块 nginx-sticky-module:基于 cookie 做粘性,效果最接近商业版,但它是社区维护的第三方模块,每次 nginx 版本升级,都要担心模块是否还兼容,编译环境是否还健壮。
- nginx plus(商业版):官方提供了完整的 sticky 功能,但要付费订阅,很多团队没有这个预算。
这个对照表直观一些:
| 方案 | 是否原生 | 粘性粒度 | 主要问题 |
|---|---|---|---|
| ip_hash | 原生 | IP 级 | 负载不均、IP 变化失效 |
| hash | 原生 | 自定义 key | 不是会话级 |
| nginx-sticky-module | 第三方 | Cookie 级 | 兼容性风险 |
| nginx plus sticky | 商业版 | Cookie 级 | 需要付费 |
| 1.29.6 原生 sticky | 原生 | Cookie 级 | 相对完善,教程少 |
2.3 1.29.6 原生粘性会话的实现思路
1.29.6 的粘性会话,走的也是 cookie 路线。核心逻辑一句话:upstream 里的每台后端服务器都有独立标识,nginx 第一次把请求转发给某台服务器后,会在响应里种下一个 cookie,记录这台服务器是谁;后续请求只要带着这个 cookie,nginx 就直接把流量指到同一台服务器。
如果那台服务器挂了怎么办?nginx 会按配置的负载均衡算法重新选择一台可用节点,同时更新 cookie 指向。这个设计比 ip_hash 聪明在两点:一是负载相对更均匀,不会出现“一个大 IP 秒杀一台机器”的尴尬;二是粘性粒度是用户级的,不受网络地址影响。
这里补充一个常见误区:粘性会话不等同于 session 共享。它只是让同一个用户的请求尽量去同一台机器,如果后端服务器崩溃,用户的 session 依然会丢。生产中最好把粘性会话和 Redis session 共享结合用,粘性是“尽量稳定”,Redis 是“最终兜底”。
2.4 适用场景和不能踩的坑
粘性会话适合这些场景:
- 没有精力重构 session 存储的存量系统
- 临时解决多实例部署带来的会话漂移问题
- 架构过渡期,先靠粘性会话撑住,后续再逐步改造成无状态服务
但使用上也有几个坑需要注意:
- cookie 名称不能和业务自己的 cookie 冲突,否则会被覆盖或干扰,导致会话保持直接失效。
- 后端机器扩缩容时,已有 cookie 可能指向已经下线的机器,nginx 会重新选节点,但用户的会话数据已经丢了,体验有损。
- 如果服务做了 CDN 缓存,要小心 CDN 是否会缓存带 Set-Cookie 的响应。
- 使用 HTTPS 时,cookie 建议加上 secure 属性,避免明文传输。
3. 配置实操:从编译到粘性会话上线
3.1 编译安装 nginx 1.29.6 的完整过程
先用源码编译的方式走一遍。为什么推荐源码编译?因为发行版仓库里的 nginx 版本通常比较旧,主线版的新功能往往要等很久才会被打进系统仓库。源码编译还能自己控制模块,遇到问题也方便排查。
以 CentOS / AlmaLinux 系为例,先装依赖:
yum install -y gcc make pcre-devel zlib-devel openssl-devel然后下载源码并解压:
wget https://nginx.org/download/nginx-1.29.6.tar.gz tar zxvf nginx-1.29.6.tar.gz cd nginx-1.29.6configure 的时候,除了常规的 SSL、gzip 等模块,记得把粘性会话模块带上:
./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_stub_status_module \ --with-http_upstream_sticky_module编译安装:
make -j$(nproc) && make install提示:如果你用的发行版或者 nginx 源码包里的模块名不完全一样,先执行
./configure --help | grep -i sticky看一下实际选项名,以你环境里的为准。
Ubuntu/Debian 系同理,依赖改成 apt 装 libpcre3-dev、zlib1g-dev、libssl-dev 即可。Windows 用户直接用官方 Windows 版二进制包,或者用 WSL 跑 Linux 环境,粘性会话在 Windows 包的可用性最好先确认一下。
3.2 粘性会话配置示例,逐行拆解
先看一个最典型的反向代理加粘性会话配置:
upstream app_cluster { sticky cookie=sticky_sid expires=2h path=/ httponly; server 10.0.0.11:8080 weight=3 max_fails=2 fail_timeout=30s; server 10.0.0.12:8080 weight=2 max_fails=2 fail_timeout=30s; } server { listen 80; server_name demo.example.com; location / { proxy_pass http://app_cluster; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }逐项解释 sticky 这一行的参数:
- cookie=sticky_sid:指定种下的 cookie 名称,建议起一个业务里不太可能出现的名字,比如 sticky_sid、nx_srv_id。
- expires=2h:cookie 有效期 2 小时。这个值要根据业务会话超时时间设置,太短会导致用户频繁被重新调度,太长可能导致扩容后流量倾斜太多。
- path=/:cookie 的作用域,默认根路径即可。
- httponly:加上之后前端 JS 读不到这个 cookie,减少被脚本窃取的风险。
- secure:如果站点是 HTTPS,建议也加上 secure,只在加密连接里传输。
上面配置里 upstream 的 server 行还带了 weight 权重,说明粘性会话可以跟权重、健康检查参数正常组合,不会互相冲突。
3.3 从 ip_hash 平滑迁移的步骤
如果你的集群现在用的是 ip_hash,想迁移到新的粘性会话,不要直接改配置重启,建议按这个步骤来:
- 先在测试环境验证新配置,用 curl 看 Set-Cookie 是否正常下发。
- 在预发环境灰度一台节点,观察 error.log 没有异常。
- 上线时把 upstream 里的 ip_hash 改成 sticky 配置,执行
nginx -t && nginx -s reload。 - 观察一段时间:客户端日志、后端日志、nginx 日志的会话分布是否均匀。
- 如果线上出现问题,一条命令回滚回去:把 sticky 改回 ip_hash,reload 即可。
迁移过程中最可能遇到的问题,是某些客户端或 CDN 会把带 cookie 的响应缓存起来,导致不同用户共享同一个 cookie。排查方法很简单:用 curl 模拟请求,看响应的 Set-Cookie 是否每个用户都不一样。
3.4 和负载均衡算法组合的实战思路
粘性会话不是孤立功能,它是和 upstream 的负载均衡算法配合工作的。默认算法是轮询,也可以在 upstream 里显式用 least_conn 让请求优先发给连接数少的机器。实际项目中我建议这样组合:
- 后端机器配置差异不大:轮询 + sticky,简单直接。
- 后端机器配置差异大:加权轮询 + sticky,让高配机器多接流量。
- 后端有长连接请求:配合 keepalive,减少反复建连的消耗。
upstream ws_cluster { sticky cookie=sticky_ws path=/; server 10.0.0.21:9001; server 10.0.0.22:9001; keepalive 32; } server { listen 9000; proxy_pass http://ws_cluster; proxy_http_version 1.1; proxy_set_header Connection ""; }这段配置是给 WebSocket 服务做的粘性会话加 keepalive,热词里也有人提到“代理 freeSwitch 的 ws 端口”,WebSocket 场景下粘性会话尤其重要,因为长连接一旦建立,后续消息能不能稳定到达同一台后端,直接决定推送功能是否正常。
4. 性能与稳定性的其他升级点
4.1 除了粘性会话,1.29.6 还改了这些
粘性会话是这次版本的大头,但不是全部。从 changelog 里能看到的其他改进,比较值得关注的还有几块:
- QUIC/HTTP3 继续增强:连接迁移、流调度的细节做了优化,对走 HTTP3 的用户体验会有改善。如果有站点开了 HTTP3,升级后建议重跑一轮压测。
- SSL 相关修复:针对一些边界情况下的握手失败、内存释放问题做了修正。生产环境跑大量 HTTPS 的站点,这个值得留意。
- upstream keepalive 行为优化:连接复用的判定逻辑更完善,减少后端连接数波动。
- 部分内存分配和 worker 子进程退出路径的修复:这些属于“看不见但很重要”的改动,能降低偶发 worker 异常退出的概率。
注意:既然是主线版,这些都还不是稳定版的最终形态,建议升级后在测试环境多观察一段时间的 error.log。
4.2 快速做一轮性能压测
无论你是想验证粘性会话还是验证版本整体的性能,升级完第一件事是压测。分享一套我自己常用的快速压测流程:
先装 wrk:
yum install -y git make && \ git clone https://github.com/wg/wrk.git && cd wrk && make压测命令:
wrk -t8 -c200 -d60s --latency http://your-domain/关注三个指标:
- Requests/sec:整体吞吐。
- Latency 的 p99:尾延迟,比平均值更有参考意义。
- 错误率:如果出现大量非预期 5xx、超时,说明配置可能有问题。
压测结果不要只看平均延迟,还要看 p99 甚至 p999。比如平均延迟 20ms 很漂亮,但 p99 可能飙到 800ms,这说明系统在极端请求下还是存在瓶颈。用 wrk 的 --latency 参数跑完会输出完整的延迟分布,直接看 p99 那一行就行。另外,压测时间不要太短,30 秒以内的数据波动很大,至少跑 60 秒以上。
压测时记得先把系统最大文件描述符调大,否则连接数一高就会碰到 limit:
ulimit -n 65535 sysctl -w net.core.somaxconn=65535在同样的配置、同样的压力模型下,把 1.29.6 和旧版本各压一轮对比,才谈得上“性能提升”。
4.3 稳定性与安全方面的建议
新版本带来的稳定性提升,需要靠日志和数据来确认。建议升级后关注这几个点:
- error.log 里是否有新的 warn 或 crit 级别报错。
- worker 进程是否长时间稳定,没有异常重启。
- 系统负载和之前对比是否下降,尤其是大量 HTTPS 长连接场景。
安全方面,无论版本多新,底线动作不能省:及时更新 OpenSSL 库、关闭不必要的模块、定期用工具扫描配置。如果之前站点用了自签名证书,升级后重新生成一次证书,顺便检查证书链和过期时间。
5. 升级流程与常见问题排查
5.1 平滑升级步骤,照着做不会翻车
升级 nginx 最怕的就是线上服务断掉。如果你已经是编译安装的 nginx,官方其实提供了平滑升级机制,利用信号完成新老版本切换,请求几乎不会中断。步骤我整理成可以照抄的流程:
先备份:
cp -r /etc/nginx /etc/nginx.bak.$(date +%Y%m%d%H%M%S) cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak编译好新版 nginx 后,先用-t检查新配置:
/usr/local/nginx/sbin/nginx -t -c /etc/nginx/nginx.conf然后向正在运行的 nginx 发送信号,启动新版本进程:
kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid)此时新老版本会短暂共存。确认新版本 worker 正常后,让老的 worker 优雅退出:
kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)观察一段时间,一切正常后,彻底退出老版本:
kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)如果发现问题需要回滚,一句命令就能切回老版本:
kill -HUP $(cat /usr/local/nginx/logs/nginx.pid.oldbin)整个过程的核心思路:新老版本共用同一个配置目录和 pid 文件逻辑,通过信号切换,避免直接重启带来的连接中断。
5.2 常见问题速查表
| 现象 | 原因 | 解决办法 |
|---|---|---|
| nginx -t 报 unknown directive "sticky" | 编译时没有包含粘性会话模块 | 检查 configure 是否带模块选项,重新编译 |
| 浏览器里看不到 Set-Cookie | cookie 被 CDN 缓存、或者 nginx 配置没生效 | 先 reload,再用 curl -I 检查响应头 |
| 用户还是会跳登录 | 粘性会话只是尽量保证同一后端,后端挂了 session 依然丢 | 结合 Redis 做 session 共享兜底 |
| 升级后 HTTP3 压测报 ERR_QUIC_PROTOCOL_ERROR | 浏览器和 nginx QUIC 实现版本兼容问题 | 清理浏览器 QUIC 缓存,或更新浏览器,关注 nginx 后续修复 |
| 扩了后端机器但流量一直不过去 | 老 cookie 都指向旧节点 | 等 cookie 过期,或临时清掉业务侧的 cookie,配合权重观察 |
| upstream keepalive 参数不生效 | 没设置 proxy_http_version 1.1 或 Connection 头 | 补上proxy_http_version 1.1; proxy_set_header Connection ""; |
| 一个端口想部署多个 Web 系统 | server_name 没区分或 location 匹配冲突 | 用不同的 server_name 或 location 前缀拆分,避免正则冲突 |
| 访问出现 403 | 目录权限不足或 autoindex 配置问题 | 检查运行用户对站点目录的读权限,或用 autoindex on 开启目录浏览 |
5.3 一些过来人的避坑细节
编译安装最怕的是“缺模块”。很多人首次编译没注意 configure 选项,上线后想加 sticky 模块,只能重新编译,非常折腾。我的习惯是在 configure 之前把所有需要的模块列成清单,编译完第一时间用/usr/local/nginx/sbin/nginx -V确认模块列表。
用 systemd 管理 nginx 的人,要注意 ExecReload 那行的写法。很多 systemd unit 文件里写的是ExecReload=/usr/bin/kill -s HUP $MAINPID,但如果你手动管理 pid,一定要注意 pid 文件路径是否正确,否则平滑升级时可能找不到新老 pid。
容器环境下升级,建议直接用官方镜像的新 tag,然后重新编排服务。需要注意配置文件卷的挂载方式,最好是让镜像只读、配置全部由外部卷提供,这样升级时配置不会漂移。
还有一个细节容易被忽视:升级之前,先看 nginx 官方 changelog 里有没有提到和你现有配置相关的“行为变更”。比如某个参数默认值改了、某个指令被废弃,这些小变动往往不会产生报错,但可能让线上表现出现微妙变化。
5.4 我对 1.29.6 的综合感受
最后说点个人使用感受。我在测试环境用 1.29.6 跑了一个模拟集群,配置粘性会话从改配置到验证通过,十分钟左右搞定,比我之前折腾 nginx-sticky-module 时编译报错、加载模块、排查兼容性问题要省心太多。性能上,在同配置同压力模型下,和旧版相比没有负优化,长连接场景下的表现还更稳了一些。
我的建议是:如果团队里刚好有基于 nginx 的负载均衡需求,又长期被会话保持问题困扰,1.29.6 值得认真评估。但生产环境升级别冲动,先在灰度环境完整跑一轮压测和业务回归,确认新功能满足预期之后,再按平滑升级流程操作。毕竟 nginx 是流量入口,稳字当头永远是第一位的。