在前面一篇博客中介绍了【Nginx+tomcat 负载均衡、动静分离】,这种架构对于有状态的应用来说会存在一定问题:由于nginx采用round bin算法(轮训调度),固每次刷新页面nginx都会将请求分发到不同的tomcat服务器上,且每次的JSESSIONID都是不一样的。于是,对于有状态的应用来说,同一用户的请求调度到不同后端tomcat服务器,导致session信息丢失。
注:
- 如果应用是无状态的,那么这种架构还是可行的;
- 如果后端只有一个tomcat服务器,使用nginx仅为了实现动静分离,这种架构也是可行的;
对于有状态应用来说,这个问题可以通过粘性session的方式来解决。在nginx中,可以通过nginx_upstream_jvm_route(推荐)和ip_hash两种技术实现。
nginx_upstream_jvm_route技术:
1、nginx_upstream_jvm_route简介:
1)nginx_upstream_jvm_route是一个nginx的扩展模块,用来实现基于 Cookie 的 Session Sticky (粘性会话)的功能。简单来说,它是基于cookie中的JSESSIONID来决定将请求发送给后端的哪个server。原理:
- 一开始请求过来,没有带session信息,jvm_route就根据round robin的算法,发到一台tomcat上面。
- 将响应的server标识绑定到cookie中的JSESSIONID中,并返回给客户。
- nginx会根据JSESSIONID来决定由哪个后端server来处理。
2)缺点:
暂时jvm_route模块还不支持默认fair的模式。jvm_route的工作模式和fair是冲突的。对于某个特定用户,当一直为他服务的tomcat宕机后,默认情况下它会重试max_fails的次数,如果还是失败,就重新启用round robin的方式将请求分发到其他tomcat服务器上,而这种情况下就会导致用户的session丢失。
总的说来,jvm_route是通过session_cookie这种方式来实现session粘性,将特定会话附属到特定tomcat上,从而解决session不同步问题,但无法解决宕机后会话转移问题。
2、nginx_upstream_jvm_route 模块安装:
1)下载
wget http://nginx-upstream-jvm-route.googlecode.com/files/nginx-upstream-jvm-route-0.1.tar.gz
在googlecode上nginx_upstream_jvm_route就一个下载地址,是2009年的0.1版本;对于比较新的nginx(1.6以后的)使用这个版本的模块安装在patch –p0 < /path/to/jvm_route.patch 时会报错。解决办法:
- 使用svn在googlecode上去下载最新的版本(2011年有修改):svn checkout http://nginx-upstream-jvm-route.googlecode.com/svn/trunk/
- 或者到github上下载;
注:
由于版本兼容的问题,本例采用nginx-1.6.0(http://nginx.org/download/nginx-1.6.0.tar.gz)和github上的nginx_upstream_jvm_route(https://github.com/nulab/nginx-upstream-jvm-route/archive/master.zip)
2)打补丁:
解压nginx-1.6.0文件,然后进入到nginx-1.6.0目录中,执行下面命令:
[root@centOS1 nginx-1.6.0]# patch -p0 < /usr/local/nginx-upstream-jvm-route-master/jvm_route.patch
3)编译、安装:
[root@centOS1 nginx-1.6.0]# ./configure –prefix=/usr/local/nginx –add-module=/usr/local/nginx-upstream-jvm-route-master/
[root@centOS1 nginx-1.6.0]# make
[root@centOS1 nginx-1.6.0]# make install
3、nginx_upstream_jvm_route配置:
1)修改nginx配置文件中tomcat负载均衡的部分:1
2
3
4
5
6
7upstream tomcats_jvm_route
{
# ip_hash;
server 192.168.33.10:8090 srun_id=tomcat01;
server 192.168.33.11:8090 srun_id=tomcat02;
jvm_route $cookie_JSESSIONID|sessionid reverse;
}
2)修改所有tomcat中server.xml文件:
1 | 将 |
3)测试:
在tomcat01和tomcat02的webapps下,ROOT目录中建立a.jsp,内容如下(输出sessionID信息):1
2
3
4
5
6
7
8
9<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
</head>
<body>
tomcat01服务器<br/>
ID:<%=session.getId() %><br/>
</body>
</html>
我们在浏览器中输入127.0.0.1/a.jsp 就可以看到输出, 并且可以观察到无论刷新多少次,输出结果不变(nginx把用户请求粘到了某一个tomcat上)。通过浏览器F12查看网络信息,可以看到cookie中的jsesisonID值326257DA6DB76F8D2E38F2C4540D1DEA.tomcat1,即带上了某台tomcat的粘性。
参考:http://hanqunfeng.iteye.com/blog/1920994
ip_hash技术:
nginx中的ip_hash技术能够将某个ip的请求定向到同一台后端,这样一来这个ip下的某个客户端和某个后端就能建立起稳固的session,ip_hash是在upstream配置中定义的:1
2
3
4
5upstream backend {
server 127.0.0.1:8080 ;
server 127.0.0.1:9090 ;
ip_hash;
}
尽量不要使用这种方式,因为:
1)nginx不是最前端的服务器:
ip_hash要求nginx一定是最前端的服务器,否则nginx得不到正确ip,就不能根据ip作hash。譬如使用的是squid为最前端,那么nginx取ip时只能得到squid的服务器ip地址,用这个地址来作分流是肯定错乱的。
2)nginx的后端还有其它方式的负载均衡:
假如nginx后端又有其它负载均衡,将请求又通过另外的方式分流了,那么某个客户端的请求肯定不能定位到同一台session应用服务器上。
3)多个外网出口:
很多公司上网有多个出口,多个ip地址,用户访问互联网时候自动切换ip。而且这种情况不在少数。使用 ip_hash 的话对这种情况的用户无效,无法将某个用户绑定在固定的tomcat上 。