Nginx是一个很高效稳定的软负载均衡器,最新的版本可以负载均衡HTTP(s),TCP,UDP等多种协议的链接。一般访问量比较大一点的Web站点都会用NGINX做HTTP协议的Web负载均衡,其后端一般是多个PHP或者JAVA中间件。另外NGINX还可以和Keepalived配合防止均衡器的单点故障,这一点要强于F5,A10这一类的硬件负载均衡设备。
但是F5,A10等硬件负载均衡器虽然价格昂贵但是仍然很有市场,其中原因之一就是硬件负载均衡器比Nginx配置简单,具备图形化界面,有图形化的实时监测界面(收费版的Nginx Plux也有这个功能,但是价格更加昂贵)。但是最重要的一点,就是硬件负载均衡器有成熟的会话保持措施,这一点是Nginx的弱点。
一般来说,我们在java中都通过如下代码进行用户登录后的服务端注册,并且在用户下次请求时无需再登陆一遍,这就是Servlet的Session
HttpSession session = request.getSession(false);
session.setAttribute("data", data);
session.getAttribute("data");
使用了这种Session策略,那么Web容器比如tomcat就为当前用户生成一个SessionID,并且以这个SessionID为索引,存储这个用户相关的键值对,比如用户名,登陆时间一类的。存储在服务器的内存中。同时再response里向用户浏览器中设置一个cookie,这个cookie的名字为jsessionid,内容为服务器生成的随机数SessionID。在用户第二次请求时,将这个cookie发给服务器,服务器根据这个SessionID到内存中寻找相关数据,把用户名什么的提取出来,服务器就可以在本来无状态的HTTP连接中识别出这是哪个客户发出的请求,然后绘制相关页面。
这中Session机制使用简单方便,被使用了很长时间。但是一旦做成集群,这种方式就不灵了。以NGINX默认的轮询方式为例,用户在A服务器上登陆成功,SessionID和用户名等相关信息写入了A服务器的内存中,该用户第二次请求时被NGINX分发到了B服务器,而B服务器没用该用户的SessionID和用户名等相关信息,于是要求用户再登陆一遍。用户第二次登陆之后发送第三次请求,被NGINX分配到了A或者C服务器,于是用户又必须登陆一遍,总之这个用户一直没法登陆成功。
所以使用NGINX默认的轮询(round-robin)方式是没法做到会话保持的,如果你硬要再这种情况下做会话保持,那么就不能使用Servlet中HttpSession这个方案了。一般有如下两种方式可以选择
1、数据库存储Session。
与Servlet Session不同的是,现在SessionID和用户信息不放到服务器内存中,而放到数据库中让所有节点都可以访问。setAttribute()变成iSQL nsert语句,getAttribute()变成SQL select语句,主键就是jsessionid这个cookie的值,这样就实现了Session的共享。但是一般不推荐这样做,因为这样会给数据库带来大量的读写请求,应用服务器是负载均衡了,可是数据库这样搞就炸了,所以避免这样搞。
2、Redis,Memcached存储Session
这个的原理和数据库存储是一样的,因为你完全可以把Redis和Memcached看成一种数据库,只不过由于Redis和Memcached是把数据存放到内存中,一般不做持久化,所以IO速度要快于普通数据库,并且Redis比较容易做集群,可以防止单点故障。是目前比较流行的一种方法。同上面一样,setAttribute()变成了put语句,getAttribute()变成了get语句。另外Redis对于JAVA支持比较好,更推荐使用Redis,Memcached在PHP中有官方的支持,比JAVA中好用。
上面的方式虽然解决了集群的问题,但是如果要从F5迁移到NGINX,那么成本是很高的,因为要重写相关的代码,还要搭建Redis服务器等等,对于一开始没有考虑到集群的项目是很难做的。
那么NGINX官方对于这种情况的解决方案是什么呢?请看下面
Session persistence
Please note that with round-robin or least-connected load balancing, each subsequent client’s request can be potentially distributed to a different server. There is no guarantee that the same client will be always directed to the same server.
If there is the need to tie a client to a particular application server — in other words, make the client’s session “sticky” or “persistent” in terms of always trying to select a particular server — the ip-hash load balancing mechanism can be used.
With ip-hash, the client’s IP address is used as a hashing key to determine what server in a server group should be selected for the client’s requests. This method ensures that the requests from the same client will always be directed to the same server except when this server is unavailable.
To configure ip-hash load balancing, just add the ip_hash directive to the server (upstream) group configuration:
upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
以上出自:
http://nginx.org/en/docs/http/load_balancing.html
方法很简单,就是NGINX根据请求源的IP,固定的分配到某个地址上去,这样保证同一个IP多次分配都是同一台服务器,这样就不用考虑共享Session的问题了。可是这种解决方案是一种非常不负责任的方案。首先这样做必须确保NGINX是放在公网上的,且NGINX前面不能有其他的代理服务器,这样才能保证NGINX能够获得用户真实的IP。但是如果NGINX前面还有squid这种代理或者前面还有一个NGINX的话,那么当前NGINX收到的就是上一级代理的IP,所有IP都一个样,所以最后只有1台后端服务器被利用,根本没有做到负载均衡的要求。
即使退一步,NGINX确实是放在公网上的第一级代理,那么根据我国目前的网络状况,有很多学校,公司企业他们公网出口就一个IP。也就是近千人共用一个IP访问公网的情况非常普遍。而这正好又是一个学生选课系统或者办公系统的话,那么NGINX还是没有起到负载均衡的作用,顶多能发挥高可用的功能。
那么对于这种实际生产中碰到的问题,用NGINX应该如何解决呢。我们首先可以看一下F5是如何解决这样问题的。、
F5支持什么样的会话保持方法?
F5 Big-IP支持多种的会话保持方法,其中包括:简单会话保持(源地址会话保持)、HTTP Header的会话保持,基于SSL Session ID的会话保持,i-Rules会话保持以及基于HTTP Cookie的会话保持,此外还有基于SIP ID以及Cache设备的会话保持等,但常用的是简单会话保持,HTTP Header的会话保持以及 HTTP Cookie会话保持以及基于i-Rules的会话保持。