背景介绍:
Nginx:负载分发与反向代理,采用 Sticky 模式
Server:后端服务 * n
浏览器登陆系统后,时而能正常加载登陆后的页面;时而白屏,一直在等待加载。(其他同事称之为闪退)
排查过程:
启动两个 server,浏览器登陆测试,并监控server日志。从server1上的日志可以看出,账号登陆正常了,从server2上的日志可以看到,无法正常请求到个人信息。由此暂时可以判断为:Nginx 的 sticky 模式未生效,登陆时请求分发到的 server1,但是登陆后的请求发送到了 server2。
来看一下 Sticky 的原理:
Sticky 是 nginx 的一个模块,它是基于 cookie 的一种 nginx 的负载均衡方案,其工作过程如下:
1、客户端首次发起访问请求,nginx 接收后,发现请求头没有cookie,则以轮询方式将请求分发给后端服务器(注:cookie 是在 server 端生成的);
2、后端服务器处理完请求,生成 cookie,并将 cookie 放入响应头中,将响应数据一同返回给 nginx ;
3、Nginx 在 server 返回的 cookie 上,添加参数 route,生成新的cookie,返回给客户端(route 的值为一段hash 值,与后端服务器对应,可以认为是对后端 ip:port 的保护);
4、客户端接收请求,并保存带 route 的 cookie ;
5、.当客户端下一次发送请求时,会带上 route,nginx 根据接收到的 cookie 中的 route 值,转发给对应的后端服务器。
系统当前的现象明显和 Sticky 的原理相违背,接下来继续使用浏览器验证:
1、首次登陆,打开登陆页,一个 get 请求,response 里产生了 route 参数(route=67f8623da60b1cc87fa0b766b10fa558)。无论使用多少浏览器,只要后端是同一个server 在响应,都会是该值,这串值代表后端server 的 ip:port。
首页下载同域名下的 kb.js 文件,请求头中没有带上一次的 route,响应头中,Nginx又返回了一个 route 值(这次很巧,和上一步一致)。也就是说 route 在浏览器上丢了!
首页下载同域名下的indexStdInfoContent.js 文件,请求头中依然没带上上一次的 route,响应头中,Nginx又返回了一个 route 值(这次和上一次不一致),这里 route 也在浏览器上丢了!
2、在登陆框中输入用户名密码并提交,一个post请求,请求头里没有有 route 参数,响应头重新写入了 route 参数。这里明显有问题,route 并没有随着请求头传给nginx,nginx 又重新给了一个route,route 在浏览器上丢了!
3、继续观察,登陆成功后,加载个人信息的过程,请求头里有 route 值了,但 route 值和登陆过程中的 route 值不一致。也就是说用户在server1上登陆了,但是获取个人信息的请求发送到了第二个server2,server2 却没有他的登陆认证。
上述显现让人很困惑,去和开发聊了一下,也没获得有用的信息,只能转回头再看一下Nginx
检查当前 Nginx 信息
如果是 Nginx 环境有问题呢?官网下载nginx及相关模块,搭建测试环境:
使用新环境验证:
1、首次登陆,打开登陆页,一个get 请求,response 里产生了 route参数(route=67f8623da60b1cc87fa0b766b10fa558)。
激动人心的地方,这里请求头里有了 route,而且和上一次请求一样。
2、在登陆框中输入用户名密码并提交,一个post请求,请求头里有了 route 参数!
3、加载个人信息的过程,请求头里有 route 值了,且和登陆过程中的 route 值一致。
ok,经过2天的试验和总结,暂时告一段落。
可以通过更换 Nginx 的方式解决这一问题,但应该不是 Nginx 造成的。
因为:还有许多其它域名也通过当前的 Nginx 环境做反向代理和负载分发,经过观察,不存在这种现象。
开发同事的解决方案
通过控制上下文根来生成 route 值,方法如下:
首先访问uri:yjsxkapp/sys/index.do,然后重定向到原首页uri:yjsxkapp/sys/xsxkapp/*default/index.do 。
当访问 yjsxkapp/sys/index.do 时,上下文根是 yjsxkapp/sys,nginx 会基于该上下文根生成一个 route值;当重定向到 yjsxkapp/sys/xsxkapp/*default/index.do 时,上下文根是 yjsxkapp/sys/xsxkapp/*default ,相当于 yjsxkapp/sys 的子集,这时候 Nginx 便不会再重新生成新的 route值。
回顾一下直接访问 uri: yjsxkapp/sys/index.do 时的几个 request 请求,其 uri 分别如下:
yjsxkapp/sys/xsxkapp/*default/index.do
yjsxkapp/sys/xsxkapp/public/kb.js
yjsxkapp/sys/xsxkapp/public/indexBS.js
yjsxkapp/sys/xsxkapp/public/index.js
yjsxkapp/sys/xsxkapp/public/indexStdInfoContent.js
(如果后端server足够多,会发现他们5个请求,每个都会生成一个独立的 route 值,可以认为他们的请求是并发的,虽然时间略有延迟)
最终结论
产生上述情况的最终原因是 老版本的 sticky 模块,在同一个浏览器的并发请求中,并不会判断这些uri是否属于相同的上下文根子集;而高版本的 sticky 模块会进行判断并合并,后续请求会使用已有 route 值。
可以升级 sticky 版本;或人为让 sticky 模块先产生父上下文根的 route 值,再进行其它请求。