1、nginx-轮询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| static ngx_http_upstream_rr_peer_t * ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp) { time_t now; uintptr_t m; ngx_int_t total; ngx_uint_t i, n, p; ngx_http_upstream_rr_peer_t *peer, *best;
now = ngx_time();
best = NULL; total = 0;
#if (NGX_SUPPRESS_WARN) p = 0; #endif
for (peer = rrp->peers->peer, i = 0; peer; peer = peer->next, i++) { n = i / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
if (rrp->tried[n] & m) { continue; }
if (peer->down) { continue; }
if (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) { continue; }
if (peer->max_conns && peer->conns >= peer->max_conns) { continue; }
peer->current_weight += peer->effective_weight; total += peer->effective_weight;
if (peer->effective_weight < peer->weight) { peer->effective_weight++; }
if (best == NULL || peer->current_weight > best->current_weight) { best = peer; p = i; } }
if (best == NULL) { return NULL; }
rrp->current = best;
n = p / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); rrp->tried[n] |= m;
best->current_weight -= total;
if (now - best->checked > best->fail_timeout) { best->checked = now; }
return best; }
|
2、nginx-ip_hash
1 2 3 4 5 6 7 8 9 10
| upstream rrBackend { ip_hash; server localhost:8001 weight=1; server localhost:8002 weight=2; server localhost:8003 weight=3; }
location /rr { proxy_pass http: }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data) { ngx_http_upstream_ip_hash_peer_data_t *iphp = data;
time_t now; ngx_int_t w; uintptr_t m; ngx_uint_t i, n, p, hash; ngx_http_upstream_rr_peer_t *peer;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get ip hash peer, try: %ui", pc->tries);
ngx_http_upstream_rr_peers_rlock(iphp->rrp.peers);
if (iphp->tries > 20 || iphp->rrp.peers->single) { ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers); return iphp->get_rr_peer(pc, &iphp->rrp); }
now = ngx_time();
pc->cached = 0; pc->connection = NULL;
hash = iphp->hash;
for ( ;; ) {
for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) { hash = (hash * 113 + iphp->addr[i]) % 6271; }
w = hash % iphp->rrp.peers->total_weight; peer = iphp->rrp.peers->peer; p = 0;
while (w >= peer->weight) { w -= peer->weight; peer = peer->next; p++; }
n = p / (8 * sizeof(uintptr_t)); m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
if (iphp->rrp.tried[n] & m) { goto next; }
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get ip hash peer, hash: %ui %04XL", p, (uint64_t) m);
ngx_http_upstream_rr_peer_lock(iphp->rrp.peers, peer);
if (peer->down) { ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer); goto next; }
if (peer->max_fails && peer->fails >= peer->max_fails && now - peer->checked <= peer->fail_timeout) { ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer); goto next; }
if (peer->max_conns && peer->conns >= peer->max_conns) { ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer); goto next; }
break;
next:
if (++iphp->tries > 20) { ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers); return iphp->get_rr_peer(pc, &iphp->rrp); } }
iphp->rrp.current = peer;
pc->sockaddr = peer->sockaddr; pc->socklen = peer->socklen; pc->name = &peer->name;
peer->conns++;
if (now - peer->checked > peer->fail_timeout) { peer->checked = now; }
ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer); ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
iphp->rrp.tried[n] |= m; iphp->hash = hash;
return NGX_OK; }
|
3、hash
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| upstream myapp { hash $http_x_real_ip; # 使用 X-Real-IP 头部的值进行哈希 server backend1.example.com weight=1; server backend2.example.com weight=2; }
upstream myapp { hash $http_x_forwarded_for; # 使用 $http_x_forwarded_for 头部的值进行哈希 server backend1.example.com weight=1; server backend2.example.com weight=2; }
|
当在 Nginx 配置中使用 hash $http_x_forwarded_for;
作为负载均衡的键时,hash用的就是真实客户端ip,ngx_http_complex_value
函数将用于计算 X-Forwarded-For
HTTP 请求头的值,并将该值赋给 hp->key
。以下是该过程的详细说明:

- 复杂值初始化:
- 在 Nginx 配置阶段,当遇到
hash $http_x_forwarded_for;
配置时,相关的配置处理函数(如 ngx_http_upstream_hash
)将初始化一个 ngx_http_complex_value_t
结构体,这里即 hcf->key
。
- 请求处理阶段:
- 当一个请求到达并需要进行上游处理时,
ngx_http_upstream_init_hash_peer
函数被调用。
- 执行复杂值:
ngx_http_complex_value
函数被用来执行 hcf->key
中定义的复杂值,这个复杂值就是 $http_x_forwarded_for
。
- 计算哈希键:
ngx_http_complex_value
函数解析 $http_x_forwarded_for
,这通常意味着它将获取请求的 X-Forwarded-For
头的值。
- 值的变化:
- 在执行
ngx_http_complex_value(r, &hcf->key, &hp->key)
之前,hp->key
是未初始化的。
- 执行后,如果函数返回
NGX_OK
,则 hp->key
将包含 X-Forwarded-For
头的值,这可能是一个单一的 IP 地址或者一个 IP 地址列表,具体取决于 X-Forwarded-For
头的内容。
- 错误处理:
- 如果
ngx_http_complex_value
函数返回 NGX_ERROR
,这通常意味着在尝试获取或计算 X-Forwarded-For
头的值时出现了问题,比如内存分配失败。在这种情况下,ngx_http_upstream_init_hash_peer
函数将返回 NGX_ERROR
,导致当前请求的上游处理初始化失败。
- 调试日志:
- 如果
ngx_http_complex_value
成功执行,将记录一条调试日志,显示 “upstream hash key” 以及计算出的键值。
- 继续处理:
- 如果
ngx_http_complex_value
成功,函数将继续执行,hp->key
将用于后续的哈希计算和对等体选择过程。
总结来说,if (ngx_http_complex_value(r, &hcf->key, &hp->key) != NGX_OK) { return NGX_ERROR; }
这段代码是用来检查 ngx_http_complex_value
函数是否成功执行,并根据执行结果决定是否继续处理请求。如果 X-Forwarded-For
头存在且格式正确,hp->key
将被赋予相应的值;如果获取头信息失败或在执行过程中遇到错误,请求处理将被中止,并返回错误状态。
初始化:
1 2 3 4 5 6 7 8 9 10 11
| tatic ngx_command_t ngx_http_upstream_hash_commands[] = {
{ ngx_string("hash"), NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12, ngx_http_upstream_hash, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL },
ngx_null_command };
|
设置值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| static char * ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_upstream_hash_srv_conf_t *hcf = conf;
ngx_str_t *value; ngx_http_upstream_srv_conf_t *uscf; ngx_http_compile_complex_value_t ccv;
value = cf->args->elts;
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &hcf->key;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; }
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
if (uscf->peer.init_upstream) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "load balancing method redefined"); }
uscf->flags = NGX_HTTP_UPSTREAM_CREATE |NGX_HTTP_UPSTREAM_WEIGHT |NGX_HTTP_UPSTREAM_MAX_CONNS |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN;
if (cf->args->nelts == 2) { uscf->peer.init_upstream = ngx_http_upstream_init_hash;
} else if (ngx_strcmp(value[2].data, "consistent") == 0) { uscf->peer.init_upstream = ngx_http_upstream_init_chash;
} else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[2]); return NGX_CONF_ERROR; }
return NGX_CONF_OK; }
|
应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| static ngx_int_t ngx_http_upstream_init_hash_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) { ngx_http_upstream_hash_srv_conf_t *hcf; ngx_http_upstream_hash_peer_data_t *hp;
hp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t)); if (hp == NULL) { return NGX_ERROR; }
r->upstream->peer.data = &hp->rrp;
if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) { return NGX_ERROR; }
r->upstream->peer.get = ngx_http_upstream_get_hash_peer;
hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
if (ngx_http_complex_value(r, &hcf->key, &hp->key) != NGX_OK) { return NGX_ERROR; }
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "upstream hash key:\"%V\"", &hp->key);
hp->conf = hcf; hp->tries = 0; hp->rehash = 0; hp->hash = 0; hp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
return NGX_OK; }
|
4、sticky
4.1、简介
Sticky(会话粘性)模块的核心目标是在负载均衡场景下,将同一客户端的所有请求“粘”到一台后端服务器上,避免因多次随机或轮询调度引起的会话状态丢失或不一致。它通过在客户端与 Nginx 之间交换一个小巧的标识符(通常是 HTTP Cookie,也可选用 HTTP Header 或 URL 参数)来实现这一机制。
- 模块演进
- 最早的实现往往基于客户端 IP(IP Hash),但在 NAT、代理大量存在的环境里,IP 并不能准确区分用户,且不支持同一用户多设备切换场景。
- 基于 Cookie 的 Sticky 引入后端可控的标识符(SID),无需共享后端 session 存储,也不会因客户端 IP 变化而失效。
- 工作范式
- 初始化阶段:为每台后端 server 生成一个唯一的 SID(MD5);
- 请求阶段:检查客户端是否携带有效 SID,无则走正常负载均衡并记下所选 server;
- 响应阶段:向客户端下发
Set-Cookie: <name>=<SID>
,或同等手段;
- 后续请求:客户端自动携带该 SID,Nginx 直接根据 SID 映射到固定的后端 server。
- 优缺点对比
- 优点:无需后端共享存储、可精确控制;支持 HTTPS 下的 Secure、HttpOnly;可与健康检查一起使用;兼容多路径、多子域场景。
- 缺点:Cookie 丢失或被清除后会重新分配;首次请求仍然有调度抖动;对 Cookie 攻击(Rebinding)需加签名或加密。
4.2、使用示例
示例 1:基础 Cookie 粘性
1 2 3 4 5 6 7 8 9 10 11 12 13
| upstream backend { server 10.0.0.1:8080; server 10.0.0.2:8080; session_sticky cookie=session_id expires=24h path=/; } server { listen 80; server_name example.com;
location / { proxy_pass http://backend; } }
|
- 说明:使用名为
session_id
的 Cookie,实现 24 小时粘性。
示例 2:基于自定义变量的路由
1 2 3 4 5 6 7 8 9 10 11
| map $remote_addr $route_hash { default ""; 192.168.0.0/16 backend1; 10.10.0.0/16 backend2; }
upstream backend { server 10.0.0.1:8080 route=backend1; server 10.0.0.2:8080 route=backend2; session_sticky cookie=sb route=$route_hash; }
|
- 说明:通过
map
指令自定义 $route_hash,客户端 IP 在特定网段下直接粘到指定后端。
示例 3:多子应用共存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| nginx复制编辑upstream appA { server a1:8000; server a2:8000; session_sticky cookie=appA_sid path=/appA domain=.example.com; } upstream appB { server b1:9000; server b2:9000; session_sticky cookie=appB_sid path=/appB domain=.example.com; } server { listen 443 ssl; server_name example.com;
location /appA/ { proxy_pass http://appA; } location /appB/ { proxy_pass http://appB; } }
|
- 说明:不同子应用使用不同 Cookie 名与 Path,互不干扰。
4.3、原理分析
3.1 配置解析与初始化
当 Nginx 启动或执行热加载时,会依次解析所有配置文件中的指令。对于 session_sticky
指令,Nginx 会调用模块中定义的处理函数 ngx_http_upstream_session_sticky
,将用户在 upstream {}
块中指定的参数(如 cookie
名称、route
变量、domain
、path
、expires
等)存入相应的配置结构体(ngx_http_upstream_ss_srv_conf_t
)。
随后,Nginx 会在整体 HTTP 模块的主初始化函数 ngx_http_upstream_init_main_conf
中,遍历每一个 upstream
块,并对其调用 peer.init_upstream
回调。由于 session_sticky
在上一步已将 uscf->peer.init_upstream
指向了 ngx_http_upstream_session_sticky_init_upstream
,此时就会执行该函数。
在 ngx_http_upstream_session_sticky_init_upstream
中,模块会为当前 upstream
下的每一个后端 server
调用 ngx_http_upstream_session_sticky_set_sid
,利用服务器名称(或自定义的 route
参数)生成并缓存一个唯一的 SID(Session ID),并完成内部哈希表或链表等数据结构的初始化,以便后续快速查找与路由。整个流程确保:
- 指令参数 在配置阶段被正确解析并保存在模块配置里;
- 回调注册 在解析完成后被挂载到 Nginx upstream 的初始化流程中;
- SID 生成 和相关数据结构在配置加载时一次性完成,不再在请求路径上重复计算。
3.2 SID 的生成与作用
SID(Session ID)用于唯一标识后端服务器,确保同一用户的请求被路由到相同的服务器。在 ngx_http_upstream_session_sticky_set_sid
函数中,SID 是通过对服务器名称进行 MD5 哈希计算生成的:
1 2 3 4
| ngx_md5_init(&md5); ngx_md5_update(&md5, s->name->data, s->name->len); ngx_md5_final(buf, &md5); ngx_hex_dump(s->sid.data, buf, 16);
|
生成的 SID 是一个 32 字节的十六进制字符串,与服务器绑定,确保在服务器重启或配置重载后保持不变。这种设计保证了在 Nginx 重启或热加载配置时,相同服务器始终对应相同的 SID,而不同服务器则拥有不同的 SID,从而实现稳定的一一绑定。
SID 会存储在 ngx_http_ss_server_t
结构的 sid
字段中,用于后续请求的快速匹配。在请求处理阶段,模块无需再次进行哈希计算,只需将客户端 Cookie 中传回的 SID 与各后端 sid
字段进行对比,即可迅速定位到目标服务器,实现高效的粘性路由。
3.3 请求中的 Cookie 读取
当客户端发送请求时,模块会在 ngx_http_session_sticky_get_cookie
中处理整个 Cookie
头部(HTTP 请求头中的单行,包含多对 name=value
),并 从中查找键名与配置中一致的项(默认为 session
,也可通过 cookie=xxx
自定义)。例如:
1 2 3
| GET /resource HTTP/1.1 Host: www.example.com Cookie: session=dcf207e1a28a5e6f4b2d9fc0d1e5e6c3; userid=42; theme=light
|
- 按照 RFC 6265 的定义,
Cookie
头部由以分号+空格分隔的 cookie-pair
列表组成;服务器解析时会拆分此头部,获得各个 name=value
对
- 模块代码会遍历所有
cookie-pair
,比较键名(session
)与配置值,一旦匹配便提取该项的 值 作为 SID
- 若客户端未携带该键名的 Cookie,或提取到的值与任何后端服务器的预生成 SID 不匹配,则视为“无有效 SID”。
3.4 使用 Cookie 值进行路由
在 ngx_http_upstream_session_sticky_get_peer
中,模块根据提取到的 SID:
- 在内部维护的服务器列表(每个元素保存了启动时由
ngx_http_upstream_session_sticky_set_sid
计算好的固定 SID)中进行查找。
- 若找到了与该 SID 完全相同的条目,则直接将请求路由到对应的后端服务器,实现会话粘性。
- 若未找到匹配项(可能因为后端下线、SID 被篡改或过期),模块会退回到默认的负载均衡算法(如轮询 round‑robin),并在后续响应阶段下发新的 SID
这种基于从整体 Cookie
头部提取特定字段并直接映射到服务器的方式,既符合 HTTP 状态管理规范,也保证了高效的请求转发。
3.5 响应中的 Cookie 设置
在响应阶段,若检测到“无有效 SID”或首次访问,模块会在 ngx_http_session_sticky_header_filter
中插入一个 Set-Cookie
头,将所选服务器的 SID 返回给客户端:
1 2 3
| HTTP/1.1 200 OK Content-Type: application/json Set-Cookie: session=dcf207e1a28a5e6f4b2d9fc0d1e5e6c3; Path=/; Domain=.example.com; Expires=Wed, 15 May 2025 12:00:00 GMT
|
- 根据Set-Cookie 规范,每个
Set-Cookie
头仅设置一对 cookie-pair
并可附加属性,如 Path
、Domain
、Expires
等
- 浏览器在后续同域且满足路径匹配的请求中,会自动在请求头
Cookie
中包含此 session=...
项(仅发送 name=value
部分,属性不会带回)
- 这就完成了从“无 SID → 选 server → 下发 SID → 客户端携带 SID → 再次路由”的完整闭环。
4.4、总结
Tengine 的 session_sticky
模块通过以下三步实现会话粘性路由:
- 配置初始化阶段:为每个后端 server 生成并固定一个唯一的 SID(MD5 哈希)
- 请求阶段:从整体
Cookie
头部中提取与配置键名一致的项(如 session
),将其值作为 SID,与后端列表匹配,实现“粘性”路由。
- 响应阶段:在首次或失效情况下,通过
Set-Cookie
下发选中服务器的 SID,确保客户端在后续请求中带上正确的 session
项
这种设计既遵循了 HTTP 状态管理机制,又在 Nginx 的高性能代理框架中保证了极低的路由开销和高度的可扩展性。只需合理配置 cookie
、path
、domain
、expires
等参数,即可满足大多数有状态应用的会话粘性需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 客户端请求进来 │ ▼ ┌────────────────────────────┐ │ 检查请求中是否带有 sticky cookie │ └────────────────────────────┘ │ ┌─────────────┴─────────────┐ ▼ ▼ 有 cookie 没有 cookie │ │ ▼ ▼ 提取 cookie 中的 sid 从 upstream 选择后端(如 round-robin) │ │ ▼ ▼ 查找 sid 是否匹配某个 server 为选中的 server 计算 sid │ │ ┌─────┴──────┐ │ ▼ ▼ ▼ 匹配成功 匹配失败 设置 sid 到响应 Set-Cookie │ │ │ ▼ ▼ ▼ 请求被转发到对应 server fallback 转发 响应客户端 │ ▼ 客户端保存 sticky cookie
|
4.5、实际的问题
1、 SID 生成原理
在session_sticky
模块中,每台后端服务器的 SID 在配置初始化阶段一次性生成,不再在请求处理时重复计算。生成逻辑为:将服务器名称(s->name
,既可以是 IP:端口
,也可以是自定义的 route
值)作为输入,调用 Nginx 自带的 MD5 接口计算哈希,然后将 16 字节的二进制哈希转换为 32 字节的十六进制字符串,存入 s->sid
字段。这种设计保证了,只要配置不变,同一服务器永久对应同一 SID,即便 Nginx 重启或热加载配置,SID 也保持不变,便于后续快速匹配
2、 多 upstream
+ 同名 Cookie 导致的覆盖场景
当在同一域名下为多个不同路径或业务的 upstream
块都启用了相同 Cookie 名称(如 session
),且未做任何作用域区分时,会在浏览器端出现 “不断被覆盖” 的问题:
**首次访问 /A
**:
**随后访问 /B
**:
浏览器行为:
- 若两条
Set-Cookie
指令同名同域同路径,则后者直接覆盖前者;若路径不同,浏览器会分别存储两条条目,但在请求时只发送与当前路径最匹配的那一条。
结果:
- 访问
/A
、/B
时 Cookie 值交替覆盖或切换,粘性路由无法生效
3、 解决办法
方案一:不同 Cookie 名称
为不同 upstream
指定互不干扰的 Cookie 名称,确保浏览器分别存储:
1 2 3 4 5 6 7 8 9 10
| upstream backendA { session_sticky cookie=SESSION_A route=$route_hash; server a1:8081 route=a1; server a2:8081 route=a2; } upstream backendB { session_sticky cookie=SESSION_B route=$route_hash; server b1:8082 route=b1; server b2:8082 route=b2; }
|
backendA
下发 SESSION_A=…
,backendB
下发 SESSION_B=…
,互不覆盖
方案二:同名 Cookie + 不同 Path
在同一个域名下,用相同名称但为不同路径设置 path
属性,浏览器会区分存储:
1 2 3 4 5 6 7 8 9 10
| upstream backendA { session_sticky cookie=session path=/A domain=example.com expires=1h; server a1:8081; server a2:8081; } upstream backendB { session_sticky cookie=session path=/B domain=example.com expires=1h; server b1:8082; server b2:8082; }
|
- 访问
/A
时只发送 session
(Path=/A);访问 /B
时只发送对应的那条。
方案三:合并 upstream + 变量路由
将所有后端合并到一个组,通过 map
或 route
变量决定路由,然后用单一 Sticky 管理会话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| map $request_uri $target { ~^/A backendA; ~^/B backendB; } upstream backend { server a1:8081 route=a1; server a2:8081 route=a2; server b1:8082 route=b1; server b2:8082 route=b2; session_sticky cookie=session route=$route_hash; }
server { location / { proxy_pass http://backend; } }
|
- 通过
$route_hash
(可以映射自 $request_uri
或其他变量)实现对不同路径的路由,再由同一 Cookie 维护粘性 。