灰度引流不能只靠sticky session,它仅保证同Cookie请求打到同一后端,不支持按Cookie内容条件路由;实现version=canary分流需用RewriteCond+RewriteRule显式匹配并代理,同时隔离sticky session作用域。

灰度引流不能只靠 stickysession,它不负责分流
stickysession 是会话保持机制,不是灰度控制开关。它只保证「同一个 Cookie 值的请求始终打到同一台后端」,但不会根据 Cookie 内容做条件路由。想实现「version=canary 的请求进灰度池,其余走默认集群」,必须绕过负载均衡器的自动调度,用 mod_rewrite 或 mod_headers 提前拦截并重写代理目标。
常见错误是把 stickysession=VERSIONID 当成灰度开关——结果只是让每个 VERSIONID 值固定绑定某台机器,所有值仍均匀分布在全部后端上,根本没达到分流效果。
- 灰度本质是「条件代理」,需在
ProxyPass之前完成判断 - Apache 不解析 Cookie 值做逻辑分支,必须显式用
RewriteCond提取 - 若后端返回的
Set-Cookie: VERSIONID=canary是动态生成的,注意响应头改写需配合mod_headers+Header edit避免覆盖
用 RewriteCond + RewriteRule 实现 Cookie 值匹配引流
这是最直接、无需额外模块的方式,适用于 Apache 2.2+。关键点在于:匹配请求头中的 Cookie 字段,而非响应 Cookie;且正则要能容忍空格和多 Cookie 并存场景。
示例配置(放在虚拟主机或目录上下文中):
RewriteEngine On
RewriteCond %{HTTP_COOKIE} (?:^|;\s*)version=canary(?:;|$) [NC]
RewriteRule ^/(.*)$ http://gray-backend:8080/$1 [P,L]
ProxyPass / http://default-cluster/
ProxyPassReverse / http://default-cluster/
说明:
-
%{HTTP_COOKIE}是原始请求头,不是解析后的键值对,所以必须用正则匹配完整字符串 -
(?:^|;\s*)和(?:;|$)确保能匹配version=canary出现在 Cookie 开头、中间或结尾 -
[NC]忽略大小写,防止客户端发Version=canary被漏掉 -
[P,L]表示代理且终止后续规则,避免被后面的ProxyPass拦截
避免灰度流量被 stickysession 错误劫持
如果同时启用了 stickysession(比如用于主集群的 JSESSIONID),而灰度后端也返回同名会话 Cookie,Apache 可能将灰度请求错误地重新绑定到主集群节点——因为 stickysession 在 ProxyPass 阶段生效,且无作用域隔离。
解决方案:
- 灰度后端使用独立 Cookie 名,如
GRAY_SESSIONID,并在主集群stickysession参数中排除它:stickysession=JSESSIONID|jsessionid - 在灰度代理规则末尾加
env=NO_STICKY,再用<if></if>条件块包裹灰度ProxyPass,彻底隔离粘性逻辑(Apache 2.4.13+) - 更稳妥的做法:灰度路径单独开一个子路径(如
/v2/),用ProxyPass /v2/ http://gray-backend/,完全避开主集群的stickysession作用域
生产环境必须处理的三个细节
灰度上线后最容易出问题的不是逻辑,而是边界情况:
-
Cookie值含特殊字符(如=、%)时,浏览器会自动 URL 编码,RewriteCond必须匹配编码后形式,建议统一用%{HTTP_COOKIE}+[NC]+ 宽松正则,避免写死%3D这类编码 - 前端通过 JS 设置 Cookie 时若未指定
domain和path,可能导致部分请求不携带该 Cookie,灰度失效;建议后端接口统一返回Set-Cookie: version=canary; Path=/; Domain=.example.com - 健康检查探针(如
/health)若被RewriteCond匹配,可能误导到灰度后端导致检测失败;应在RewriteCond前加一行RewriteCond %{REQUEST_URI} !^/health排除


















