搭建Tomcat集群&通过Redis缓存共享session的一种流行方案

  |   0 评论   |   0 浏览

为什么要共享 session?

我们使用单台 Tomcat 的时候不会有共享 sesssion 的疑虑,只要使用 Tomcat 的默认配置即可,session 即可存储在 Tomcat 上。
90350865592ba7404d258_articlexjpg

但是随着业务的扩大,增加 Tomcat 节点构成 Tomcat 集群大势所趋,分布式带来了增加更大规模并发请求的优势,但是也随之到来了一个问题,每个 Tomcat 只存储来访问自己的请求产生的 session,如果 Tomcat-A 已经为客户端 C 创建了会话 session,那么 Tomcat-B 并不知道客户端已与集群中的 Tomcat-A 产生了会话,在访问时就会为 C 再创建一份 session,如果是基于 session 的验证会话权限的接口(如用户登录认证后才可访问的数据接口),将会导致在访问集群中不同节点的时候重复认证。session 的不共享导致原来的会话管理机制在 Tomcat 集群中无法工作。
2908076365592ba74074ec8_articlexjpg

所以,如果有一个 Tomcat 集群都能访问的公共 session 存取区就好了,基于这个概念,我们想到了使用 Redis 来做这个 session 公共存取区,这样子的话就有一个统一管理回话的地方了。回看我们上文提到的例子,如果 Tomcat-A 已经为客户端 C 创建了会话 session,这个 session 信息会直接存储在公共的 Redis 里面,那么 Tomcat-B 就可以到公共 session 存储区里获得已为 C 产生的 session,这样的结果是集群的公共 session 存取区在逻辑上就像一个 Tomcat 的内部 session 存取区一样了。
30457203592ba7407e0b4_articlexjpg

怎么做呢?

有了上述基本的概念,我们就要开始真正施行了。

1. 持久化 Tomcat Session 到 Redis 中

Tomcat 提供了一个开放的 session 管理和持久化的org.apache.catalina.session.ManagerBase,继承这个抽象类并做一些简单的配置,即可让你的 session 管理类接管 Tomcat 的 session 读取和持久化。当然,我们在这里使用了一个流行的开源项目:
https://github.com/ran-jit/tomcat-cluster-redis-session-manager
,它已经为我们准备好了这样的一个管理类,只要将这个管理类配置在 Tomcat 中即可发挥功能。它可以帮助我们将 Tomcat 的 session 存入我们指定的 Redis,甚至支持 Redis 在 sentinel 模式调度的 Redis 集群,稍后我们也将详述这样的 Redis 集群该如何部署。

使用这个项目非常简单,如果在 Tomcat6 或 Tomcat7 下部署,直接使用项目 release 出的 jar 文件到 Tomcat 的lib下即可,准确来说还需要引入它的其他几个依赖(括号中为建议的):

tomcat-cluster-redis-session-manager-VERSION.jar(v3.0)
commons-pool2-VERSION.jar(v2.4.2)
jedis-VERSION.jar(v2.9.0)
commons-logging-VERSION.jar(v1.2)

conf/redis-data-cache.properties文件复制到Tomcat/conf/redis-data-cache.properties,然后,引入后需要在 Tomcat 下修改conf/context.xml

<Valve className="tomcat.request.session.redis.SessionHandlerValve" />
		<Manager className="tomcat.request.session.redis.SessionManager" />

确认tomcat/conf/web.xml下的超时配置:

<session-config>
   <session-timeout>60<session-timeout>
<session-config>

这样一来我们的 Tomcat 即可把 session 的管理交由我们配置的 Redis 来处理。

2. Nginx 反向代理的负载均衡

虽然这不是本文的重点,但是使用负载均衡在搭建集群的过程中重要性不言而喻,使用 nginx 默认的轮询机制,我们可以将前端的浏览器请求转发到不同的 Tomcat 实例上。
首先来讲讲正向代理和反向代理,一言以蔽之:正向代理帮助内网 client 访问外网 server 用,反向代理将来自外网 client 的请求 f 转发到到内网 server。
最实际的区别是使用二者时正向代理需要用户主动配置,而反向代理对用户透明,不需要用户做主动配置。
「代理」是指代人理事,即代理服务器是为其他人或机器服务的。
正向代理是替内网中的用户访问外网服务的(即代替用户去访问外网),用户和外网之间的沟通全部交由正向代理服务器完成,用户的请求不发给外网服务器而发给代理服务器让其代为处理,这个过程是隐藏用户的。
反向代理是为真正的服务节点机器服务的(即代替真正的服务节点机器去提供服务),代理服务器接收来自外界的请求,并将请求转给真正的服务节点机器,用户不与真正的机器打交道
,这个过程是隐藏真正的服务实例机器的。

nginx 可以作为高效的反向代理服务器,同时起到了负载均衡的作用。如果想要使用反向代理 Tomcat 集群的负载,方法也非常简单,只需要在其配置nginx.conf中将负载的 Tomcat 集群的实际地址加入 upstream,并将 locate 导向配好的 upstream 即可:

http{
    ...
    upstream tomcats {
        server      <tomcat1_ip>:<tomcat1_port>;
        server      <tomcat2_ip>:<tomcat2_port>;
        ...
        server      <tomcatn_ip>:<tomcatn_port>;
      }
    ...
    server {
        listen       80;
		   server_name  localhost;
        ...
        location / {
            proxy_pass      http://tomcats;
        }
      }
}

进入 nginx 根目录,使用 start nginx 启动 Nginx,默认的轮询机制将每次请求都发至不同的 Tomcat 实例上,以此实现负载均衡。

3. 基于 sentinel 的 Redis 集群搭建

上文介绍的方法其实已经可以搭建一个完整的 Tomcat 集群了,如果系统想要一个更安全可靠的环境,那么 nginx 其实也可以做集群,这里略去不说,我们想要说的是 Redis 集群。

上面我们已经说到 Redis 是 session 的公共存储区,如果 Redis 不幸挂掉的话将会导致致命的问题,因为无 session 源可取,Tomcat 中有 session 读取的接口会直接报错。所以搭建一个 Redis 集群还是很有必要的,幸好 Redis 对分布式 HA 的搭建支持得很好,原生即有一套 sentinel 哨兵机制即可用。

以 sentinel 模式启动的 Redis 实例起到了监控者的作用,Redis 集群以 master-slave 的模式启动,消息不再直接发给 Redis 实例,而是发给 sentinel,由 sentinel 同步至所有的 Redis 实例,如果出现 redismaster 实例挂掉的情况,会由 sentinel 发现,根据配置还可以由 sentinel 自己组成的集群去选举产生新的 master,新的 master 将会承担起作用,起到了灾难自动回恢复的作用。

这里来说一下 sentinel 集群的配置:
我们使用两个 Redis 实例来组成 master-slave,需要三个 sentinel 组成哨兵集群来监控两个 Redis 实例,在 master 出现问题的时候选举产生新的 master。路径假设如下:

redis1redis2
sentinel1sentinel2sentinel3

配置redis1/redis.conf为 master:

bind 127.0.0.1
port 6379

配置redis2/redis.conf为 redis1 的 slave:

bind 127.0.0.1
port 6379
slaveof <redis1_ip> 6379

配置sentinel1/redis-sentinel.conf

port 26379    
sentinel monitor mymaster <redis1_ip> 6379 2

指定 redis1 为 master,slave 信息会在启动后被 sentinel 监听到并自动写入到配置文件中,因此不需要手动写入,最后的 quorum 表示当有 2 个 sentinel 判断 master 挂掉的时候即可选举 slave 为新的 master。sentinel2sentinel3配置相同。

这样之后启动 Redis 和 sentinel 集群,即可构建一个高可用的 Redis 集群。可以尝试一下把redis1这个 master 挂掉,sentinel 就会探查到并且在 2 个 sentinel 都探查到的时候即会选举产生新的 master:

# +monitor master mymaster <redis1-ip> 6379 quorum 2
# +sdown master mymaster <redis1-ip> 6379

同时我们的 Tomcat 配置Tomcat/conf/redis-data-cache.properties启用 sentinel

#-- Redis data-cache configuration

#- redis hosts ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, ....
redis.hosts=127.0.0.1:6379

#- redis password
# redis.password=

#- set true to enable redis cluster mode (default value: false)
redis.cluster.enabled=false

#- set true to enable redis sentinel mode (default value: false)
**redis.sentinel.enabled=true**
# redis sentinel master name (default value: mymaster)
**redis.sentinel.master=mymaster**

#- redis database (default value: 0)
#redis.database=0

#- redis connection timeout (default value: 2000 ms)
#redis.timeout=2000

#- enable redis and standard session mode. (default value: false)
# If enabled,
#   1. Must be enabled sticky session in your load balancer configuration. Else this manager may not return the updated session values.
#   2. Session values are stored in local jvm and redis.
#   3. If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced.
lb.sticky-session.enabled=true

这个集群搭建完成后,我们的系统将会更为稳定:
4139826312592ba7407981c_articlexjpg

参见:https://www.cnblogs.com/gcjava/p/6601293.html