当前位置: 首页 > news >正文

企业级灰度发布架构:基于Nginx的精细化流量治理与平滑演进实践

文章目录

  • 基于Nginx权重实现灰度发布原理
    • 1. 灰度发布讲解
    • 2. Nginx权重分流原理
    • 3. 精细化流量控制策略
    • 4. 灰度发布流程图
  • 基于Nginx权重实现灰度发布实验
    • 1. 创建Version1服务
    • 2. 创建Version2服务
    • 3. 编写Nginx配置文件
    • 4. 访问测试
    • 5. 基于特定Cookies进行访问
  • 问题思考
    • 1. 公共服务连接影响
    • 2. 服务连续性问题
  • 基于Cookie的会话保持
    • 1. 修改Nginx配置文件
    • 2. 服务验证
    • 3. 配置文件解读

在这里插入图片描述

基于Nginx权重实现灰度发布原理

1. 灰度发布讲解

灰度发布(Gray Release)是一种渐进式的软件发布策略,它允许将新版本服务逐步推向生产环境,而不是一次性替换整个系统。这种发布方式的核心目标是降低发布风险保证系统稳定性,同时能够实时监控新版本的运行状态。

优点:

  • 风险控制:将新版本可能带来的故障影响范围控制在最小
  • 用户体验:避免全量发布导致的整体服务中断或性能下降
  • 实时反馈:通过小流量测试收集真实用户反馈和性能数据
  • 快速回滚:发现问题时可立即切回稳定版本,减少损失

2. Nginx权重分流原理

Nginx通过upstream模块实现负载均衡,这是灰度发布的基石。其核心配置如下:

vim /etc/nginx/conf.d/gray-release.conf 
# 定义后端服务集群 - 增加健康检查参数
upstream backend_servers {server 127.0.0.1:8080 weight=50 max_fails=2 fail_timeout=30s;  # Version1 - 增加容错server 127.0.0.1:8081 weight=50 max_fails=2 fail_timeout=30s;  # Version2 - 增加容错
}

工作原理:

  • Nginx根据权重比例计算每个服务器的概率分布

  • 对于每个新请求,Nginx按照权重概率选择后端服务器

  • 权重算法基于平滑加权轮询,确保流量分布均匀且符合比例

  • max_fails=2:最多失败2次后标记服务器不可用

  • fail_timeout=30s:失败后30秒内不再向该服务器转发请求

  • 30秒后会自动重试,如成功则重新加入负载均衡池

3. 精细化流量控制策略

基于Cookie的精准路由:

# 优化基于Cookie的灰度发布配置 - 使用更精确的正则匹配
map $cookie_gray_release $backend_group {default "backend_servers";   # 默认使用权重分配"~*v1"  "version1_server";  # 使用正则确保匹配包含v1的cookie值"~*v2"  "version2_server";  # 使用正则确保匹配包含v2的cookie值
}
  • 内部测试:测试人员通过设置Cookie值强制访问特定版本
  • 用户体验:特定用户群体优先体验新功能
  • 问题排查:重现用户问题时保持环境一致性

基于IP段的灰度发布:

geo $gray_ip_group {default         "backend_servers";192.168.1.0/24  "version2_server";  # 内部测试网络访问新版本10.0.0.100      "version2_server";  # 特定测试机访问新版本
}
  • 内部办公网络优先体验新版本
  • 特定机房或地域逐步放开

监控模块

通过Nginx status模块实时监控:

	location /nginx_status {stub_status on;      # 开启状态模块access_log off;      # 可选:关闭此位置的访问日志allow 127.0.0.1;     # 允许访问的IP地址,例如本地allow 192.168.16.0/24; # 允许的内网网段,按需设置deny all;            # 拒绝其他所有IP访问,重要!}# 状态信息:
Active connections: 3 
server accepts handled requests307 307 487 
Reading: 0 Writing: 1 Waiting: 2
  • Reading:正在读取请求头的连接数(待处理请求)
  • Writing:正在发送响应的连接数(处理中请求)
  • Waiting:保持连接但空闲的连接数
  • accepts/handled/requests:总接受/处理/请求数,用于计算成功率

4. 灰度发布流程图

在这里插入图片描述

流量入口 (客户端 → Nginx)

  • 动作:所有外部客户端的请求首先统一到达部署好的 Nginx 网关服务器(监听8082端口)。
  • 角色:Nginx 在这里扮演了 “流量调度员”“策略执行者” 的核心角色。它是整个灰度系统的大脑。

流量调度 (Nginx 权重分发)

  • 动作:Nginx 根据预先配置的权重规则(upstream模块),将接收到的请求按比例分发到后端的两个服务实例。 90% 的请求被转发到运行 旧版本 V1.0 的服务端(A服务器,监听8080端口)。 10% 的请求被转发到运行 新版本 V1.1 的服务端(B服务器,监听8081端口)。
  • 特点:这个过程对用户是完全透明的,用户无需感知也控制不了自己访问的是哪个版本,分流是随机的。

业务处理 (服务端 → 数据层)

  • 动作:两个版本的服务实例接收到请求后,各自独立处理业务逻辑。它们都需要读写同一个数据库/Redis
  • 核心要求:这里存在一个灰度发布的关键前提,即版本V1.1对数据的任何修改(数据库表结构、缓存格式等)都必须是向后兼容的,不能影响V1.0的正常读写,否则会导致数据错乱。

基于Nginx权重实现灰度发布实验

注意:安装Nginx版本要≥1.18.0,若公网访问,请放行所需端口。

1. 创建Version1服务

sudo mkdir -p /var/www/html/{v1,v2,static}
vim /var/www/html/v1/index.html

Version1服务:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>服务版本 1.0</title><style>body { font-family: Arial, sans-serif; background-color: #f0f8ff; text-align: center; padding: 50px;}.version { color: #ff6b6b; font-size: 2.5em; margin-bottom: 20px;}.features { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);max-width: 600px;margin: 0 auto;}</style>
</head>
<body><div class="version">Version 1.0 - 稳定版</div><div class="features"><h2>当前功能特性</h2><ul style="text-align: left; display: inline-block;"><li>基础用户管理功能</li><li>标准报告生成</li><li>基础数据分析</li><li>传统界面设计</li></ul><p><strong>服务器端口: 8080</strong></p><p>更新时间: 2024-01-01</p></div>
</body>
</html>

在这里插入图片描述

2. 创建Version2服务

vim /var/www/html/v2/index.html 

Version2服务:

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>服务版本 2.0 - 新特性</title><style>body { font-family: Arial, sans-serif; background-color: #fff0f5; text-align: center; padding: 50px;}.version { color: #4ecdc4; font-size: 2.5em; margin-bottom: 20px;}.features { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);max-width: 600px;margin: 0 auto;}.new-badge { background: #4ecdc4; color: white; padding: 2px 8px; border-radius: 10px; font-size: 0.8em; margin-left: 5px;}</style>
</head>
<body><div class="version">Version 2.0 - 新特性版 <span class="new-badge">NEW</span></div><div class="features"><h2>全新功能特性</h2><ul style="text-align: left; display: inline-block;"><li>智能用户推荐系统 <span class="new-badge"></span></li><li>AI报告自动生成 <span class="new-badge"></span></li><li>实时数据分析看板</li><li>现代化界面设计 <span class="new-badge"></span></li><li>移动端优化支持</li></ul><p><strong>服务器端口: 8081</strong></p><p>更新时间: 2025-10-09</p></div>
</body>
</html>

在这里插入图片描述

3. 编写Nginx配置文件

vim /etc/nginx/conf.d/gray-release.conf 
# 定义后端服务集群 - 增加健康检查参数
upstream backend_servers {server 127.0.0.1:8080 weight=50 max_fails=2 fail_timeout=30s;  # Version1 - 增加容错server 127.0.0.1:8081 weight=50 max_fails=2 fail_timeout=30s;  # Version2 - 增加容错
}# 基于Cookie的灰度发布配置 - 使用更精确的正则匹配
map $cookie_gray_release $backend_group {default "backend_servers";   # 默认使用权重分配"~*v1"  "version1_server";  # 使用正则确保匹配包含v1的cookie值"~*v2"  "version2_server";  # 使用正则确保匹配包含v2的cookie值
}# 基于IP的灰度配置示例(可选策略)
geo $gray_ip_group {default         "backend_servers";192.168.1.0/24  "version2_server";  # 特定IP段访问新版本10.0.0.100      "version2_server";  # 特定IP访问新版本
}upstream version1_server {server 127.0.0.1:8080 max_fails=2 fail_timeout=30s;
}upstream version2_server {server 127.0.0.1:8081 max_fails=2 fail_timeout=30s;
}# 主服务器配置
server {listen 8083;server_name localhost;# 设置全局访问日志格式,便于区分流量access_log /var/log/nginx/gray_access.log ;error_log /var/log/nginx/gray_error.log;# 静态资源服务location /static/ {root /var/www/html;expires 30d;add_header Cache-Control "public, immutable";}location /nginx_status {stub_status on;      # 开启状态模块access_log off;      # 可选:关闭此位置的访问日志allow 127.0.0.1;     # 允许访问的IP地址,例如本地allow 192.168.16.0/24; # 允许的内网网段,按需设置deny all;            # 拒绝其他所有IP访问,重要!}# 主要请求处理 - 增强的灰度路由location / {# 可根据需要选择使用Cookie或IP策略:# 当前使用Cookie策略,如需切换可注释下一行并使用IP策略proxy_pass http://$backend_group;# 如需使用IP策略,取消下面这行的注释:# proxy_pass http://$gray_ip_group;# 设置代理头信息proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Gray-Version $backend_group;  # 新增:向后端传递灰度版本信息# 超时设置proxy_connect_timeout 30s;proxy_send_timeout 30s;proxy_read_timeout 30s;# 重试设置(谨慎使用)proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;proxy_next_upstream_tries 2;}
}# Version1 服务配置 (8080端口) - 保持稳定
server {listen 8080;server_name localhost;location / {root /var/www/html/v1;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version1.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version1 Healthy\n";add_header Content-Type text/plain;}
}# Version2 服务配置 (8081端口) - 保持稳定
server {listen 8081;server_name localhost;location / {root /var/www/html/v2;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version2.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version2 Healthy\n";add_header Content-Type text/plain;}
}

启动Nginx:

# 检查Nginx的语法
nginx -t # 启动nginx
nginx -s reload 
# 可不重启Nginx达到动态更新的效果

4. 访问测试

访问8083端口,查看是否与我们定义的配置文件内定义的比例是否相同:(目前是50:50,每两次必定刷新新旧两个版本。)

在这里插入图片描述
在这里插入图片描述

后续测试和动态平滑更新均可以使用该字段进行更新。

在这里插入图片描述

如果我们想下线Version1,完成大的版本迭代,我们可以修改配置文件:

# 定义后端服务集群 - 增加健康检查参数
upstream backend_servers {server 127.0.0.1:8080 weight=10 max_fails=2 fail_timeout=30s down;  # 使用down标记服务下线完成更新server 127.0.0.1:8081 weight=90 max_fails=2 fail_timeout=30s; 
}

加上 down标记后,Nginx 在负载均衡时将完全忽略该服务器,所有新请求都会发给健康的服务器。

不过,在彻底下线旧版本的服务之前,我们要进行例行的检查,这里提供一个shell脚本进行检查:

#!/bin/bash# 服务下线预检查脚本
# 功能:检查服务状态,判断是否可以安全下线,不执行任何下线操作。set -e  # 遇到错误立即退出,确保检查的严谨性# 颜色定义,用于清晰显示结果
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # 恢复默认颜色# 检查结果汇总
CAN_DOWN=true
WARNING_MESSAGES=()
ERROR_MESSAGES=()SERVICE_ADDR="127.0.0.1:8080"  # 待下线的服务地址(Version1)
SERVICE_NAME="Version1@${SERVICE_ADDR}"echo -e "🔍 开始对 ${YELLOW}${SERVICE_NAME}${NC} 进行下线前健康检查...\n"
echo "=========================================="# 1. 检查服务进程是否存在
echo -e "1. 检查服务进程..."
PID=$(lsof -ti :8080)
if [ -n "$PID" ]; thenecho -e "   ${GREEN}✓ 服务进程存在 (PID: $PID)${NC}"
elseecho -e "   ${RED}✗ 服务进程不存在${NC}"CAN_DOWN=falseERROR_MESSAGES+=("服务进程已不存在,无需执行下线操作,但请排查为何进程消失。")
fi# 2. 检查服务健康接口
echo -e "2. 检查服务健康接口 (HTTP)..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://${SERVICE_ADDR}/health" --connect-timeout 3 --max-time 5 || echo "000")
if [ "$HTTP_CODE" -eq 200 ]; thenecho -e "   ${GREEN}✓ 健康接口返回 200${NC}"
elif [ "$HTTP_CODE" -eq 000 ]; thenecho -e "   ${RED}✗ 无法连接到健康接口 (服务可能已完全不可用)${NC}"WARNING_MESSAGES+=("服务健康接口无法连接,下线操作风险较低,但需确认是否为预期情况。")
elseecho -e "   ${YELLOW}⚠ 健康接口返回非200状态码: ${HTTP_CODE}${NC}"WARNING_MESSAGES+=("服务健康接口异常 (HTTP ${HTTP_CODE}),建议先排查此问题再下线。")CAN_DOWN=false # 若非200状态码认为不健康,可根据您的策略调整
fi# 3. 检查Nginx upstream中该服务的当前状态
echo -e "3. 检查Nginx配置中该服务的状态..."
NGINX_CONF="/etc/nginx/conf.d/gray-release.conf" # 请修改为您的实际配置文件路径
if grep -q "server ${SERVICE_ADDR}" "$NGINX_CONF"; thenif grep -q "server ${SERVICE_ADDR}.*down" "$NGINX_CONF"; thenecho -e "   ${YELLOW}⚠ 服务在Nginx配置中已被标记为 'down'${NC}"WARNING_MESSAGES+=("该服务在Nginx配置中已处于 'down' 状态,请确认是否已执行过下线操作。")elseecho -e "   ${GREEN}✓ 服务在Nginx配置中处于活跃状态${NC}"fi
elseecho -e "   ${RED}✗ 在Nginx配置中未找到该服务${NC}"ERROR_MESSAGES+=("在Nginx配置文件中未找到目标服务,请检查配置文件路径和服务地址是否正确。")CAN_DOWN=false
fi# 4. 检查服务在负载均衡中的活跃请求(核心修改部分)
echo -e "4. 检查服务在负载均衡中的活跃请求..."
STATUS_URL="http://127.0.0.1:8083/nginx_status"# 使用curl获取状态页内容,并提取Reading和Writing的数值
STATUS_OUTPUT=$(curl -s $STATUS_URL)
READING=$(echo "$STATUS_OUTPUT" | awk '/Reading/ {print $2}')
WRITING=$(echo "$STATUS_OUTPUT" | awk '/Writing/ {print $4}')# 显式打印出关键指标
echo "   当前连接状态: Reading=$READING, Writing=$WRITING"# 核心判断逻辑:只有当读写操作均为0时,才认为可以安全下线
if [ "$READING" -eq 0 ] && [ "$WRITING" -eq 0 ]; thenecho -e "   ${GREEN}✓ 当前无活跃请求(Reading + Writing = 0)${NC}"CAN_DOWN=true
elseecho -e "   ${RED}✗ 当前仍有活跃请求(Reading: $READING, Writing: $WRITING${NC}"ERROR_MESSAGES+=("服务仍有 [$READING] 个读取请求和 [$WRITING] 个写入请求,请等待请求处理完毕后再下线。")CAN_DOWN=false
fi# 5. 检查系统资源(可选,判断是否存在其他潜在问题)
echo -e "5. 快速系统资源检查..."
LOAD_AVG=$(uptime | awk -F 'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
MEM_USAGE=$(free | grep Mem | awk '{printf "%.1f", $3/$2 * 100.0}')
echo -e "   系统平均负载: $LOAD_AVG"
echo -e "   内存使用率: $MEM_USAGE%"
# 简单阈值判断,您可以根据服务器情况调整
if (( $(echo "$LOAD_AVG > 5" | bc -l 2>/dev/null) )); thenecho -e "   ${YELLOW}⚠ 系统负载较高${NC}"WARNING_MESSAGES+=("当前系统负载较高 [${LOAD_AVG}],请评估下线服务对整体性能的影响。")
fiecho ""
echo "=========================================="
echo -e "📋 ${YELLOW}检查结果汇总:${NC}"# 输出警告和信息消息
if [ ${#WARNING_MESSAGES[@]} -gt 0 ]; thenecho -e "\n${YELLOW}⚠ 警告信息:${NC}"for msg in "${WARNING_MESSAGES[@]}"; doecho "  - $msg"done
fiif [ ${#ERROR_MESSAGES[@]} -gt 0 ]; thenecho -e "\n${RED}❌ 错误信息:${NC}"for msg in "${ERROR_MESSAGES[@]}"; doecho "  - $msg"done
fi# 最终决策
echo ""
if [ "$CAN_DOWN" = true ]; thenecho -e "${GREEN}✅【结论:可以下线】${NC}"echo -e "所有关键检查已通过。当前服务状态允许执行下线操作。请您手动执行下线命令。"
elseecho -e "${RED}❌【结论:不建议下线】${NC}"echo -e "存在关键问题,当前下线服务可能存在风险。请先解决上述错误信息中提到的问题。"
fiecho ""
echo "=========================================="
echo "检查完成于: $(date)"
检查项检查内容为何重要
服务进程服务进程是否仍在运行。确认操作对象存在,避免对已停止的服务进行无效操作。
健康接口服务的 /health端点是否返回200。这是判断服务内部状态是否健康的直接证据,比进程是否存在更重要。
Nginx配置状态服务在Nginx配置中是否已被标记为 down防止重复操作,提醒您当前配置状态。
活跃连接(建议)服务是否还有未处理完的请求。最关键的一环。如果仍有活跃连接,强制下线会中断用户请求。
系统资源当前系统负载和内存使用情况。评估下线单个服务对整体系统稳定性的潜在影响。

执行该脚本后,会简单的为我们分析当前环境是否可以完全去除旧版本的服务:(注意配置文件路径和服务暴露端口等)

🔍 开始对 Version1@127.0.0.1:8080 进行下线前健康检查...==========================================
1. 检查服务进程...✓ 服务进程存在 (PID: 1740674
1740675
1740676
1740677
1740678)
2. 检查服务健康接口 (HTTP)...✓ 健康接口返回 200
3. 检查Nginx配置中该服务的状态...✓ 服务在Nginx配置中处于活跃状态
4. 检查服务在负载均衡中的活跃请求...当前连接状态: Reading=0, Writing=1✗ 当前仍有活跃请求(Reading: 0, Writing: 15. 快速系统资源检查...系统平均负载: 0.02内存使用率: 9.1%==========================================
📋 检查结果汇总:❌ 错误信息:- 服务仍有 [0] 个读取请求和 [1] 个写入请求,请等待请求处理完毕后再下线。❌【结论:不建议下线】
存在关键问题,当前下线服务可能存在风险。请先解决上述错误信息中提到的问题。==========================================
检查完成于: Thu Oct  9 14:21:18 CST 2025

当Read和Write均为0后即可下线服务,完成灰度发布:

curl http://127.0.0.1:8083/nginx_status
Active connections: 1 
server accepts handled requests5 5 5 
Reading: 0 Writing: 1 Waiting: 0 

5. 基于特定Cookies进行访问

Cookie 名称: gray_release(由 $cookie_gray_release变量定义)
在这里插入图片描述

打开测试页面:在浏览器(Chrome/Firefox/Edge)中访问您的网站 http://您的域名或IP:8083

打开开发者工具:按 F12键打开开发者工具。

找到 Application/C存储 面板Chrome/Edge:切换到 Application标签页 -> 左侧找到 Cookies并展开 -> 选择您的网站。Firefox:切换到 存储标签页 -> 左侧找到 Cookie并展开 -> 选择网站。

添加 Cookie:点击 +或右键点击空白处选择 Add

填写以下信息:

Name: gray_releaseValue: v2(如果想测试新版) 或 v1(如果想测试旧版)

Domain: 域名或IP(如 localhost、IP,通常会自动填充)

Path: /(确保对所有路径生效)

刷新页面:添加完成后,刷新页面,您的所有请求就会被定向到指定的版本,不会受到灰度发布策略所影响

在这里插入图片描述

问题思考

1. 公共服务连接影响

问题:如果新版本(V2)服务所需的数据库表结构与旧版本(V1)不一致,如何操作?是让V1和V2连接同一个数据库,还是各自连接不同的数据库?

回答:绝对必须使用同一个数据库,并且必须保证数据结构的向后兼容。 让V1和V2服务连接不同的数据库是一个非常危险且错误的选择,会导致严重的数据不一致和业务逻辑混乱。

2. 服务连续性问题

问题:一个用户第一次访问被灰度策略分配到了V2服务,他的后续请求是会持续访问V2,还是可能被随机跳转到V1服务?这个服务链路是否是连续、一致的?

回答:默认的基于权重的灰度策略下,用户的后续请求是随机的,可能会在V1和V2之间跳跃。 但可以通过会话保持技术来解决,强制同一用户的请求始终落在同一个版本上。Nginx默认的权重分流策略是无状态的。对每一个新到来的请求都会根据权重比例重新“掷一次骰子”,独立决定将其转发到V1还是V2。它不关心这个请求来自谁,也不关心这个用户之前的请求去了哪里。

解决方案:

基于Cookie的会话保持

  • Nginx可以提供sticky模块或使用hash算法,主动给用户浏览器设置一个Cookie,里面包含版本信息。
  • 流程:用户第一次访问,Nginx按权重分配并为其设置一个Cookie(如route_version=v2)。该用户后续所有请求都带着这个Cookie,Nginx根据Cookie值直接将其路由到V2,不再重新计算权重。

正是这样的思考,就有了文章接下来的部分:

基于Cookie的会话保持

1. 修改Nginx配置文件

修改Nginx配置文件,本次的文件为生产级Nginx实现灰度发布文件:

# 1) Seed selection: prefer logged-in user id cookie (COOKIE_USER_ID), otherwise use client IP.
#    Replace COOKIE_USER_ID below with your actual login cookie name or leave as empty string "" to always use remote_addr.
# If your login cookie is "user_id", change the first map line to map $cookie_user_id ...
map $cookie_user_id $seed {"" $remote_addr;default $cookie_user_id;
}# 2) Percent-based stable assignment based on seed (decided in request phase)
#    Adjust 50%/50% as needed. Uses the seed so same seed maps consistently.
split_clients "$seed" $percent_choice {10%     version1_server;   # change 10% to your desired percentage for V1*       version2_server;
}# 3) Cookie mapping (if client already has gray cookie)
map $cookie_gray_version $cookie_group {default "";"v1" "version1_server";"v2" "version2_server";
}# 4) Final selection: cookie takes precedence, otherwise percent_choice
map "$cookie_group:$percent_choice" $final_group {"~^version1_server:" "version1_server";"~^version2_server:" "version2_server";default $percent_choice;
}# 5) Compose Set-Cookie string depending on whether cookie already exists AND scheme
#    If cookie is empty and final_group==versionX, generate cookie.
#    Use Secure+SameSite=None when over HTTPS; otherwise omit Secure and use SameSite=Lax.
map "$cookie_gray_version:$final_group:$scheme" $set_gray_cookie {"~^:version1_server:https$" "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version2_server:https$" "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version1_server:http$"  "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";"~^:version2_server:http$"  "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";default "";
}# Optionally, if you want to set Domain=.yourdomain.com (for sharing across subdomains),
# you can create a map that appends "; Domain=.yourdomain.com" to the cookie strings for https/http cases.
# Example (uncomment and adjust your domain if needed):
# map $set_gray_cookie $set_gray_cookie_domain {
#     default "$set_gray_cookie; Domain=.yourdomain.com";
# }
# Then use add_header Set-Cookie $set_gray_cookie_domain always; (instead of $set_gray_cookie)# 6) Upstream definitions
upstream version1_server {server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;keepalive 32;
}upstream version2_server {server 127.0.0.1:8081 max_fails=3 fail_timeout=10s;keepalive 32;
}# 7) Logs (include decision & cookie for observability)
log_format grayformat '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" "$http_user_agent" ''upstream=$upstream_addr final_group=$final_group percent="$percent_choice" ''cookie_gray="$cookie_gray_version" setcookie="$set_gray_cookie"';# 8) Main server block
server {listen 8083;server_name YOUR_DOMAIN;  # replace with IP or domain as appropriateaccess_log /var/log/nginx/gray_access.log grayformat;error_log  /var/log/nginx/gray_error.log warn;# Health/status and static can be added as neededlocation /nginx_status {stub_status on;access_log off;allow 127.0.0.1;deny all;}location / {# Expose decision headers (debugging/observability)add_header X-Gray-Decision $final_group always;add_header X-Gray-Percent $percent_choice always;# Add Set-Cookie only when map produced a non-empty cookie string# If you need to avoid emitting empty Set-Cookie header when $set_gray_cookie is empty,# consider installing the headers_more module and conditionally set headers there.add_header Set-Cookie $set_gray_cookie always;# Proxy to selected upstreamproxy_pass http://$final_group;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Gray-Version $final_group;# Timeouts & buffering tuning (adjust to your app)proxy_connect_timeout 5s;proxy_send_timeout 60s;proxy_read_timeout 60s;proxy_buffering on;# Failover behavior: be careful — automatic retry may mask new-version issues.# Consider removing proxy_next_upstream or limit its use in strict gray tests.proxy_next_upstream error timeout http_500 http_502 http_503 http_504;proxy_next_upstream_tries 1;}
}# 9) Backend test servers (example) - replace with real upstream or remove# Version1 服务配置 (8080端口) - 保持稳定
server {listen 8080;server_name localhost;location / {root /var/www/html/v1;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version1.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version1 Healthy\n";add_header Content-Type text/plain;}
}# Version2 服务配置 (8081端口) - 保持稳定
server {listen 8081;server_name localhost;location / {root /var/www/html/v2;index index.html;# 记录访问日志用于监控access_log /var/log/nginx/version2.access.log;}# 健康检查端点location /health {access_log off;return 200 "Version2 Healthy\n";add_header Content-Type text/plain;}
}

2. 服务验证

执行该配置文件后进行查看:

在这里插入图片描述

3. 配置文件解读

智能种子选择机制

map $cookie_user_id $seed {"" $remote_addr;default $cookie_user_id;
}
  • 创新点:优先使用已登录用户的ID作为种子,如果没有则回退到客户端IP
  • 优势:为登录用户提供更稳定的版本分配(同一用户始终访问同一版本),未登录用户按IP分配

稳定的百分比分配

split_clients "$seed" $percent_choice {10%     version1_server;*       version2_server;
}
  • 优势:基于种子值的哈希分配,确保同一用户/IP的分配结果一致
  • 可调参数:您可以轻松调整百分比(如改为5%/95%)

Cookie处理

map "$cookie_gray_version:$final_group:$scheme" $set_gray_cookie {"~^:version1_server:https$" "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version2_server:https$" "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; Secure; SameSite=None";"~^:version1_server:http$"  "gray_version=v1; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";"~^:version2_server:http$"  "gray_version=v2; Path=/; Max-Age=14400; HttpOnly; SameSite=Lax";default "";
}
  • 安全特性:HTTPS环境下:使用SecureSameSite=None,适合跨站场景HTTP环境下:使用SameSite=Lax,提供基本安全保护所有Cookie都设置HttpOnly

优先级决策逻辑

map "$cookie_group:$percent_choice" $final_group {"~^version1_server:" "version1_server";"~^version2_server:" "version2_server";default $percent_choice;
}
  • 明确优先级:现有Cookie > 百分比分配
  • 正则匹配:使用正则表达式确保精确匹配

用户体验优化

  • 会话一致性:登录用户在整个用户会话中保持同一版本
  • 跨设备支持:基于用户ID而非IP,用户在不同设备上也能获得一致体验

安全性与合规性

  • 安全Cookie设置:区分HTTP/HTTPS环境,符合现代浏览器安全要求
  • SameSite策略:防止CSRF攻击,同时支持跨站使用
  • HttpOnly标志:保护Cookie不被JavaScript访问

可观测性与调试

log_format grayformat '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" "$http_user_agent" ''upstream=$upstream_addr final_group=$final_group percent="$percent_choice" ''cookie_gray="$cookie_gray_version" setcookie="$set_gray_cookie"';
  • 详细日志:记录所有关键决策变量,便于故障排查和数据分析
  • 响应头调试:添加X-Gray-DecisionX-Gray-Percent头,便于实时调试

灵活性和可扩展性

  • 易于调整:只需修改split_clients百分比即可调整流量比例
  • 多环境支持:自动适应HTTP和HTTPS环境
  • 域名配置:提供Domain配置示例,支持跨子域名共享Cookie

至此,本篇文章就结束啦,拜拜。

http://www.dtcms.com/a/461547.html

相关文章:

  • 【滑动窗口专题】第一讲:长度最小的子数组
  • 软考-系统架构设计师 基于架构的软件开发方法详细讲解
  • 电子电气架构 --- 操作系统的基本概念
  • 苏州做网站公司电话wordpress资源分享网
  • 手机能建设网站企业的做网站
  • Unity笔记(十一)——换装、Spine骨骼动画、3D动画相关
  • 面向汽车网络安全的轻量级加密技术
  • 《投资-114》价值投资者的认知升级与交易规则重构 - 从大规模分工的角度看,如何理解“做正确的事”,即满足下游正确的需求
  • 添加一路AXI总线对DDR进行读写时,XDMA测试不通过
  • 基于python的机器学习(十)—— 评估算法(三)
  • 男女做那个的的视频网站检察院门户网站建设成效
  • Oracle的SID是什么
  • Oracle大会临近,23ai 本地版会发布吗?
  • 【Python刷力扣hot100】11. Container With Most Water
  • 通信建设网站做网站主页上主要放哪些内容
  • 《常用 IDL(接口定义语言)详解与对比》
  • 做二手房产网站多少钱河南建设工程信息网站
  • K230基础-获取触摸坐标
  • Linux应用--网络编程
  • 鸟哥的Linux私房菜 第三部分: 学习shell与shell script
  • 鸿蒙中 UDP 数据包发不出去?一文教你从权限到代码彻底排查!
  • 前端小白学习路线(参考)
  • 大连工业大学图书馆网站建设优化培训班
  • 浅谈 富文本编辑器
  • 有手机网站了还要微网站吗设计平台兼职
  • **发散创新:状态函数在编程中的深度应用与实现**在编程领域,状态函数是一个核心概
  • 【OCR】PaddleX
  • Python 元组与集合详解
  • 微信小程序的页面生命周期 以及onShow的应用场景
  • 微信小程序入门学习教程,从入门到精通,微信小程序核心 API 详解与案例(13)