nginx sticky session

Sticky session,一般翻译作会话保持,其实就是说负载均衡能做到这一点:一个用户第一次访问服务,可能是随机分配的一台upstream服务器提供服务,而这个用户的后续请求都发往第一次服务的这台机器。这样做有很多好处,比如可以提高用户数据的缓存命中率、数据一致性更容易保证等等。

Nginx本身就有一些会话保持的方法,比如ip_hash,根据请求的ip地址来哈希分配。但有些情况ip_hash是失效的,比如我们使用公司的网络,出口ip可能全都一样,这样我们访问一个外部服务其实全哈希到同一台服务器,直接失去了负载均衡的意义。Nginx Plus也有这个功能,看起来很美,唯一缺点是花钱。也有免费的sticky modulesticky 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的值。