用 Nginx + Lua 做按用户灰度发布:一次真实落地记录
一、为什么把灰度放在网关层
项目早期只有三台应用服务器,每次上线都是整批替换,出事就只能回滚。想上灰度,评估过服务网格和配置中心下发权重,但对一个五人团队来说太重:要引入新的控制面,还要改所有调用链。最后选了一个更土的办法——在 Nginx 上用 Lua 做按用户分流,改造成本不到一天,回滚只需要改一个 upstream 名字。
二、分流键比权重更难选
按 IP 分是最省事的,但同一个办公室出口 IP 相同,测试同事会集体落到灰度组,等于没灰度。按设备号分又会让同一用户在不同端上看到不一样的版本。最后用的是登录用户 ID:从 Cookie 里取 uid,取不到就退回设备指纹,再取不到就按 IP 哈希。分流比例用 uid 对一个固定盐做 crc32 再取模,保证同一个用户永远落在同一侧,不会刷新一次页面就换一个版本。
三、用 Lua 在 access 阶段改写 upstream
关键是把逻辑放在 access 阶段,而不是 rewrite,因为 rewrite 阶段还拿不到完整的请求头。脚本里先算桶号,命中灰度就把变量 upstream_name 设成 app_v2,否则设成 app_v1,再在 location 里用 proxy_pass 指向这个变量。注意 proxy_pass 带变量时 Nginx 不会自动解析域名,必须显式写 resolver,并且变量里要带上完整的 scheme 和路径,否则会出现 502 或者路径被吃掉的情况。
四、会话与缓存的一致性
分流以后最容易出的问题是数据不一致。第一是登录态:如果 session 存在应用本地内存里,用户被分到另一组就会掉线,所以灰度前必须先把 session 外置到 Redis。第二是缓存:两组服务写同一份缓存,key 里必须带上版本前缀,否则老版本写进去的旧结构会被新版本读到,序列化直接报错。第三是消息队列,灰度组的消费者如果先上线,可能消费到老版本还不认识的消息格式,稳妥做法是新增字段而不是改字段。
五、回滚与观测
灰度期间必须能一眼看出两组的表现差异。做法是在 Nginx 的日志里加一个 upstream 字段,落到日志服务后按版本分组统计错误率和 P95 耗时;同时在 Lua 里把命中的桶号写进响应头,出问题时能直接定位是哪个用户被分到了哪一组。回滚分两级:先按比例降回零,让新请求全部走老版本,观察五分钟;如果还有问题,再把 upstream 指回单组并清掉灰度机器的连接。整个过程不需要重新发版,这也是当初选网关方案最大的收益。
- 点赞
- 收藏
- 关注作者
评论(0)