****背景:HTTP协议是无状态的,为了记录用户的一些基本信息避免每次请求都需要验证用户是性能变差。客户端和服务器端基于该缺点分别做了相应的机制:cookie和session机制。但是将tomcat部署在集群中时,需要多台服务器的session保持一致,及session共享否则容易出现登录的用户在另一台服务器出现无登陆的状况,该文章将针对该问题进行研究。

****总体方案及分析:

目前解决session共享的方案有三种方案:

一 cookie机制

原理:

该方法就是将session中保存的信息加密后全部返回给客户端的cookie中,这样客户端每次发送请求时会将这些信息发送给服务器端,同一用户在不同的服务器上cookie是一样的因此不会出现session共享的问题。

优势:

该方案比较简单,容易实现,服务器的压力减小。

缺点:

1.因将很多信息存在cookie中,客户端每次请求需要将cookie中的信息传送给服务器端,所以网络请求资源占用比较多,(服务器购买带宽是一个很大费用),成本增高。归纳为带宽性能,速度问题。

2.不同的浏览器对cookie的存储大小限制不同。

3.安全性问题,session中存的都是重要性数据(账号、昵称、用户ID等),存在着泄露风险。

适用性:

基于上述优缺点的分析该方案适用于存储信息量比较少的,但不适用高访问量的情况下,每次请求浏览器都要发送session数据给服务器。一般一个Cookie大小2K的样子。

二   负载均衡

原理:

将某一用户按照一定的规则固定的分发到同一服务器,每次请求都命中同一台服务器,因此不涉及session共享。

优点:

简单容易实现,直接在服务器进行配置,不需要在代码层上做限制。

缺点:

1.负载均衡的目的:当其中的服务器不可用时,会自动的分发到可用的机子上去。然而经过上述的规则后,只要一台服务器故障后,那么所有命中该台服务器的用户将无法登录。

三  将session信息存入中间层

基本原理:

将session信息存到某一中间层,所有的应用从该中间层获取用户的基本信息。

其中cookie机制比较简单将用户信息经过加密后写入cookie中,每次请求服务器端需要从cookie中获取信息验证用户。现将对其他的两种方案做详细的研究。

****方案详解

一 负载均衡

1.nginx反向代理与负载均衡

架构图

session共享 redis session共享原理_session共享

该负载均衡用到三台服务器,一台nginx服务器,两台正式部署项目的服务器,首先在部署项目的两台服务器上部署项目的tomcat,并启动tomcat。配置防火墙端口:vim/etc/sysconfing/iptables编辑,开放8080、80端口等一些常用的端口,以及后边用到的端口都需要配置开放,不建议关闭防火墙。

2.反向代理与负载均衡配置

session共享 redis session共享原理_负载均衡_02

session共享 redis session共享原理_负载均衡_03

3.nginx负载均衡策略

session共享 redis session共享原理_session共享 redis_04

session共享 redis session共享原理_session共享_05

所以多个项目的实例中不出现session错乱的现象,nginx需要同一用户每次命中同一台服务器,需要采用ip_hash的负载均衡的模式。但是如果项目的前面还有其他的服务器进行转发的话就需要我们自定义负载均衡策略,首先获取用户的真实IP,然后将该ip进行映射到同一服务器。

添加的配置如下:

1.1在http{}中添加

http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 50m;
    client_body_buffer_size 256k;
    client_header_timeout 3m;
    client_body_timeout 3m;
    send_timeout 3m;
    proxy_buffer_size 64k;
    proxy_buffers   32 32k;
    proxy_busy_buffers_size 128k;
    proxy_connect_timeout    600;
    proxy_read_timeout       600;
    proxy_send_timeout       600;
   
   #获取用户真实IP,并赋值给变量$clientRealIP
   map $http_x_forwarded_for $clientRealIp {
   “” $remote_addr;
   ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr;
   }[S1]

 [S1]获取用户的真实ip

1.2.添加upstream

upstream app1{
       hash $clientRealIp;[S1] 
       server swh.**.com:8114[S2] ;
       server swh1.**.com:8116;[S3] 
    }

 [S1]自定义hash:根据用户真实ip进行hash

 

 [S2]双节点机器名1:端口号

 [S3]双节点机器名2:端口号

1.3 在server{}中添加

server {
        listen       8888;
        server_name swh.**.com localhost;
        proxy_redirect  http://test1.**.com/  http://test2.**.com/;
        server_name_in_redirect on;
        location /app1{
           #proxy_pass http://swh.**.com:8114/app1;
           add_header realIP $remote_addr;
           add_header REMOTE_IP $http_remote_addr;
           add_header clientRealIp $clientRealIp;
           add_header   X-Forwarded-For-1  $http_x_forwarded_for;
           add_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
           add_header backendIP $upstream_addr;
           add_header backendCode $upstream_status;
           proxy_read_timeout 120;
           proxy_set_header Host $http_host;
           proxy_set_header Remote-Addr $http_remote_addr;
           access_log logs/app1.access.log main;
           error_log logs/app1.error.log;
            
           if ($remote_addr = '192.33.12.3')
                 {
                      proxy_pass http://swh.**.com:8114;
                      break;
                 }
 
 
               if ($remote_addr  = '192.32.5.9')  
                 {
                      proxy_pass http://swh1.baidu.com:8116;
                      break;
                  }
           proxy_pass http://app1/app1;
        }
        location /mobile {
           add_header clientRealIp $clientRealIp; // 在消息头中添加信息(命中的服务器的名称:端口号)
           add_header backendIP $upstream_addr;
           add_header backendCode $upstream_status;
           proxy_pass http://mobile[S1] /mobile;
           proxy_read_timeout 120;
           proxy_set_header clientRealIp $clientRealIp;
           proxy_set_header Host $http_host;
           proxy_set_header Remote-Addr $http_remote_addr;
           access_log logs/mobile.access.log main;
           error_log logs/mobile.error.log;
        }
     
    }
}

 [S1]与upstream名保持一致

二.将session信息存入中间层

(1)nginx提供了ip_hash策略,可以保持用户ip进行hash值计算固定分配到某台服务器上,然后只要是该ip则会保持分配到该服务器上,保证用户访问的是同一台服务器上,那么session问题就不存在了。这也是解决session共享的一种方式,也称为黏性session。但是假设一台tomcat服务器挂了,那么session也会失效。所以比较好的方案是抽取session。

(2)session存在redis或者memcache,以这种方式来同步session,把session抽取出来,放到内存数据库里,解决了session共享的问题,同时读取速度也是非常之快。

session共享 redis session共享原理_session共享 redis_06

session共享 redis session共享原理_负载均衡_07