Sticky session,一般翻译作会话保持,其实就是说负载均衡能做到这一点:一个用户第一次访问服务,可能是随机分配的一台upstream服务器提供服务,而这个用户的后续请求都发往第一次服务的这台机器。这样做有很多好处,比如可以提高用户数据的缓存命中率、数据一致性更容易保证等等。
Nginx本身就有一些会话保持的方法,比如ip_hash,根据请求的ip地址来哈希分配。但有些情况ip_hash是失效的,比如我们使用公司的网络,出口ip可能全都一样,这样我们访问一个外部服务其实全哈希到同一台服务器,直接失去了负载均衡的意义。Nginx Plus也有这个功能,看起来很美,唯一缺点是花钱。也有免费的sticky module,sticky module ng,问题是Nginx要重新编译,不太方便,也可能是一些新的大坑,时间黑洞。
其实我们用各种linux发行版维护的免费版Nginx配合上游服务器也能实现同样的功能。
原理很简单:应用服务器在用户请求里写入一个约定好的cookie,带上服务器的身份信息,比如主机名。Nginx先配置好{身份信息:地址}的映射关系,拿到请求后,在转发前提取此cookie,根据身份信息转发对应的upstream。
假设我们有两台应用服务器:node1, node2。cookie长这样 Name: AUTH_SESSION_ID
, Value: a2c1dac1-5b50-4d1a-ba8f-f838222bd176.node1
,其中node1指示了cookie来自哪台主机。这是Keycloak的cookie格式,我复用一下,其实你完全可以自己定义一种,就免了下面的正则表达式提取。
Nginx配置文件
http { map $cookie_AUTH_SESSION_ID $sticky_host { default default_upstream; ~^[^\.]*\.(?<node>.*) $node; } upstream node1 { server 4.3.2.1:8080 max_fails=1 fail_timeout=3s; server 4.3.2.2:8080 backup; } upstream node2 { server 4.3.2.2:8080 max_fails=1 fail_timeout=3s; server 4.3.2.1:8080 backup; } server { listen 80; location / { proxy_pass http://$sticky_host; } } }
只需要提取cookie,转发对应的upstream,就实现了sticky session。
这里使用了map
将AUTH_SESSION_ID的最后一部分提取到变量$sticky_host
里,然后作为proxy_pass的值。