20. Tomcat - 实现Session共享服务器

7. Session共享服务器

7.1 msm

msm(memcached session manager)提供将Tomcat的session保持到memcached或者redis的程序, 可实现高可用.

项目站点: https://github.com/magro/memcached-session-manager

支持Tomcat的6.x、7.x、8.x、9.x

Tomcat的Session管理类,Tomcat版本不同
    memcached-session-manager-2.3.2.jar
    memcached-session-manager-tc8-2.3.2.jar
Session数据的序列化、反序列化类
    官方推荐kyro
    在webapp中WEB-INF/lib/下
驱动类
    memcached(spymemcached.jar)
    Redis(jedis.jar)

7.2 安装

https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration

将spymemcached.jar、memcached-session-manage、kyro相关的jar文件都放到Tomcat的lib目录中去,这个目录是$CATALINA_HOME/lib/, 对应本次安装就是/usr/local/tomcat/lib目录

asm-5.2.jar
kryo-3.0.3.jar
kryo-serializers-0.45.jar
memcached-session-manager-2.3.2.jar
memcached-session-manager-tc8-2.3.2.jar
minlog-1.3.1.jar
msm-kryo-serializer-2.3.2.jar
objenesis-2.6.jar
reflectasm-1.11.9.jar
spymemcached-2.12.3.jar

7.3 sticky模式

7.3.1 原理

sticky模式, 即前端tomcat和后端memcached有关联(粘性关系)

图片.png

特点:

session信息会分别在Tomcat和Session服务器各存一份, 保证每个Session都在Tomcat和Session各存一份

Tomcat和session服务器可以在同一台主机, 也可部署在不同的服务

工作过程:

  1. 客户端访问, 通过前端负载均衡调度到Tomcat1

  2. Tomcat1生成Session信息并保存在本地, 同时存放一份到另一台主机上的Memcached上. 即使T1坏了, 从另一台主机的Memcached上也能拿到Session

  3. 当T1坏了, 调度器会把原来用户的请求调度到另一台T2上, T2上由于没有Session, 则会查M2上的session, M2因为是从T1复制的Session, 因此会有原来T1上的所有Session.

  4. 如果T1和M1都宕机了, 第一次被调度到T1上的用户再次访问时, 会被调度到T2上, T2上由于没有Session, 那么会去查M2, M2由于从T1拿Session因此是有用户session信息的.

  5. 但是此时, 如果有新用户访问, 那么T2接受请求产生新的Session后, 由于M1挂了, 那么就会备份到本机的M2上去.

总结: sticky模式就是Tomcat和后端Session服务器都保留相同的Session信息, 而且是错位保存, 确保不管是Tomcat宕机还是Session服务器宕机都有备份信息可用. Tomcat会优先往另一台的Session服务器备份, 如果另一台的Session服务器坏了, 就备份到本地的Session服务器

另外, 调度到不同服务器上时, 只要Tomcat没有Session, 那么就不会去查另一台主机上的Memcached, 因为, 另一台服务器上Memcached保存的Session就是自己复制过去的, 所以会直接查看本地的Memecached节点保存的Session信息

7.3.2 配置过程

7.3.2.1 下载相关jar包

下载相关jar包, 参考下面官方说明的下载链接

https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration

  • Tomcat和Memcached相关包
图片.png
  • 序列化相关下载
图片.png

7.3.2.2 修改Tomcat配置

修改$CATALINA_HOME/conf/context.xml, 注意: t1配置中的failoverNode="n1", t2配置中的failoverNodes="n2"

# 以下是t1的sticky的配置
<Context>
  ...
  <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:10.0.0.82:11211,n2:10.0.0.83:11211" # n1 和 n2即Memcached节点
    failoverNodes="n1" # 在Tomcat1上, 其数据是备份到n2的, 因此, 当n2故障, 需要转到到n1
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />
</Context>

配置说明

memcachedNodes="n1:host1.yourdomain.com:11211,n2:host2.yourdomain.com:11211"

memcached的节点: n1、n2只是别名,可以重新命名。

failoverNodes故障转移节点,n1是备用节点,n2是主存储节点。另一台Tomcat将n1改为n2,其主节点是n1,备用节点是n2。

最终配置成功,可以在logs/catalina.out中看到下面的内容

[t1] de.javakaffee.web.msm.MemcachedSessionService.startInternal --------
-  finished initialization:
- sticky: true
- operation timeout: 1000
- node ids: [n2]
- failover node ids: [n1]
- storage key prefix: null
- locking mode: null (expiration: 5s)

7.3.3 实验: sticky模式, tomcat和memcached在同一台主机

图片.png
t1.tomcat.org: tomcat-memcached - 10.0.0.82
t2.tomcat.org: tomcat-memcached - 10.0.0.83
nginx 10.0.0.201

nginx配置代理负载均衡

http {
    
    upstream tomcat-server {

        server t1.tomcat.org:8080;
        server t2.tomcat.org:8080;
    }
        location ~* \.jsp$ {
    
            proxy_pass http://tomcat-server        

        }

nginx主机上配置地址解析

10.0.0.82 t1.tomcat.org
10.0.0.83 t2.tomcat.org  

在tomcat创建虚拟主机, 并把t1和t2分别设置为默认虚拟主机, 这样不管能否匹配到url或者是用ip地址访问, 都会访问到我们设定的虚拟主机, 否则会访问默认页面

t1:

    <Engine name="Catalina" defaultHost="t1.tomcat.org"> 
      <Host name="t1.tomcat.org"  appBase="/data/webapps"
            unpackWARs="true" autoDeploy="true"> 
      </Host>
mkdir /data/webapps/ROOT -pv

jsp测试页面

vim /data/webapps/ROOT/index.jsp
<%@ page import="java.util.*"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>tomcat test</title>
</head>
<body>
<div>On <%=request.getServerName() %></div>
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>                                                                        
<%=new Date()%>
</body>
</html>
chown -R tomcat.tomcat /data/webapps
systemctl restart tomcat

t2

 <Engine name="Catalina" defaultHost="t2.tomcat.org"> 
            <Host name="t2.tomcat.org"  appBase="/data/webapps"                                                                                     
                        unpackWARs="true" autoDeploy="true">
                        </Host>
mkdir /data/webapps/ROOT -pv

jsp测试页面

vim /data/webapps/ROOT/index.jsp
<%@ page import="java.util.*"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>tomcat test</title>
</head>
<body>
<div>On <%=request.getServerName() %></div>
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>                                                                        
<%=new Date()%>
</body>
</html>
chown -R tomcat.tomcat /data/webapps
systemctl restart tomcat

测试访问验证调度成功

由于目前是正常轮询机制, 所以每次都会调度到不同服务器, 并且Sessionid会一直变换

图片.png

下面配置memcached, 两台主机安装memcached

yum -y install memcached

修改监听端口

vim /etc/sysconfig/memcached
#OPTIONS="-l 127.0.0.1,::1"  

启动memcached

systemctl enable --now memcached

配置tomcat

t1:

vim /usr/local/tomcat/conf/server.xml

#在Engine添加jvmRoute字段, 这样生成的Sessionid会带有Tomcat1字段, 就知道是哪个Tomcat生成的ID了
<Engine name="Catalina" defaultHost="t1.tomcat.org" jvmRoute="Tomcat1">
#

vim /usr/local/tomcat/conf/context.xml

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:t1.tomcat.org:11211,n2:t2.tomcat.org:11211"
    failoverNodes="n1"  #failover n1是故障转移节点, 也就是一旦和T1对应的n2故障, 那么T1会往n1上做session备份
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />

t2:

vim /usr/local/tomcat/conf/server.xml

#在Engine添加jvmRoute字段, 这样生成的Sessionid会带有Tomcat2字段, 就知道是哪个Tomcat生成的ID了
<Engine name="Catalina" defaultHost="t2.tomcat.org" jvmRoute="Tomcat2">
#

vim /usr/local/tomcat/conf/context.xml

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:t1.tomcat.org:11211,n2:t2.tomcat.org:11211"
    failoverNodes="n2"  #failover n1是故障转移节点, 也就是一旦和T1对应的n2故障, 那么T1会往n1上做session备份
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />

将memcached用的相关jar传到lib目录下

t1和t2做相同设置


image.png

image.png

重启tomcat

t1和t2

systemctl restart tomcat

此时观察tomcat日志, 会发现如下报错

    Caused by: java.lang.IllegalArgumentException: Invalid failover node id n1: not existing in memcachedNodes '[n2]'.

这是因为, 在配置context.xml指定后端memcached服务器时我们用的是域名, 但是tomcat本地并没有做域名解析, 因此无法连接到后端的memcached服务器

在t1和t2做域名解析

10.0.0.82 t1.tomcat.org
10.0.0.83 t2.tomcat.org                                       

再次重启tomcat服务, 观察日志

06-Nov-2020 19:41:06.141 INFO [t1.tomcat.org-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal --------
-  finished initialization:
- sticky: true  #只要看到完成初始化, sticky: true即可说明配置成功
- operation timeout: 1000
- node ids: [n2]
- failover node ids: [n1]
- storage key prefix: null
- locking mode: null (expiration: 5s)
--------

在nignx上安装部署python3环境. 查看memcached上的session信息

yum -y install python3 python3-memcached

准备python3脚本

[01:41:25 root@nginx ~]#vim showmemcached.py

import memcache # pip install python-memcached
  
mc = memcache.Client(['10.0.0.82:11211','10.0.0.83:11211'], debug=True)                                                                                                                                                              

#stats = mc.get_stats()[0]
#print(stats)
#for k,v in stats[1].items():
#    print(k, v)

#print('-' * 30)
# 查看全部key
#print(mc.get_stats('items')) # stats items 返回 items:5:number 1
#print('-' * 30)
print(mc.get_stats('cachedump 5 0')) # stats cachedump 5 0 # 5和上面的items返回的值有关;0表示全部

测试前观察n1和n2上都是没有session信息的

图片.png
  • 从Windows浏览器发起第一次访问

可以看到第一次访问被调度到了10.0.0.82, 按照备份原则, Session信息DB73会被备份到n2上

图片.png

利用python脚本验证Sessionid DB73被备份到了n2上

图片.png

第二次访问时, 按照轮询机制, 会被调度上83上, 但是83上是没有之前82给用户生成的session信息的, 因此83会直接在本地的memcached-n2上拿到session信息, 然后把本地n2上的这条session信息给删了, 再把这条信息备份到自己对应的memcached-n1节点上. 抓包能看到效果.

图片.png
图片.png

第三次访问又被调度回82, 82之前是有该Session信息的, 因为是自己第一次生成给用户的, 因此会在Tomcat本地存放一份, 也会再存放到自己对应的n2上, 最后达到三次访问, n1和n2有相同的sessionid

图片.png
图片.png

测试故障 1: tomcat宕机

停止83上tomcat服务, 多次用同一个cookie访问, 此时就只有请求报文有cookie而响应报文是没有cookie的, 因为请求报文的cookie最开始就是82给发的, 因为第一次访问是被调度到了82上, 83停掉服务后, 访问也是访问到82上, 那么82就不会再响应一个cookie了, 之后无论访问几次都只有请求报文有cookie而响应报文没有

图片.png
图片.png

测试故障 2: memcached宕机

停止83上的memcached服务, 再次访问, 请求报文的cookie信息会是, n2-T1, 因此上次的响应报文是n2-T1, 而响应报文变成n1-T1, 因此被调度到T1上而且n1是存活的, 之后再访问, 也是只有T1响应, 也就不会再响应报文提供cookie了, 只有请求报文会携带cookie

图片.png

7.4 non-sticky模式

7.4.1 原理

non-sticky模型即前段tomcat和后端memcached无关联, 从msm 1.4.0之后开始支持non-sticky模式

session信息只存在session服务器, tomcat上生成session后, 会随机选中后端的一个memcached节点来存放session, 并且清除本地的session

Tomcat和session服务器可以在同一台主机, 也可分开放

session保存过程:

用户发请求到t1, t1产生session, 但是不保留session,而是随机找memcached存放session, 存放了session的memcache就成为了这个session的主session服务器, t1会把session再发给另一个session服务器做备份, 之后就把这个session从本地删除. 主备session服务器是针对session不同而不同的 由tomcat随机选 但是对于所有session来说, 每个session服务器都保存相同的信息

tomcat生成session, 随机找一个memcached作为该session的主服务器, 并把session发过去, 之后会把这个session再发给另一个memcached服务器作为备份, 之后删除这个session

7.4.2 memcached实现non-sticky的配置

  • 只需在sticky配置基础上, 修改context.xml文件即可

t1:

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:t1.tomcat.org:11211,n2:t2.tomcat.org:11211"
    sticky="false"
    sessionBackupAsync="false"
    lockingMode="uriPattern:/path1|/path2"                                                                                                                                          
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />


t2:

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:t1.tomcat.org:11211,n2:t2.tomcat.org:11211"
    sticky="false"                                                                                                                                                                  
    sessionBackupAsync="false"
    lockingMode="uriPattern:/path1|/path2"    
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />


systemctl restart tomcat
  • 清空两个memcached上之前的session信息
systemctl restart memcached

[02:08:49 root@nginx ~]#python3 showmemcached.py 
------------------------------
[('10.0.0.82:11211 (1)', {}), ('10.0.0.83:11211 (1)', {})]
  • 查看日志, 确定non-sticky成立
24-Mar-2021 02:23:56.100 INFO [t1.tomcat.org-startStop-2] de.javakaffee.web.msm.MemcachedSessionService.startInternal --------
-  finished initialization:
- sticky: false # false为non-sticky, true为sticky
- operation timeout: 1000
- node ids: [n1, n2]
- failover node ids: []
- storage key prefix: null
- locking mode: uriPattern:/path1|/path2 (expiration: 5s)
--------
24-Mar-2021 02:22:54.556 INFO [t2.tomcat.org-startStop-2] de.javakaffee.web.msm.MemcachedSessionService.startInternal --------
-  finished initialization:
- sticky: false
- operation timeout: 1000
- node ids: [n1, n2]
- failover node ids: []
- storage key prefix: null
- locking mode: uriPattern:/path1|/path2 (expiration: 5s)

7.4.3 访问测试

  • Windows第一次访问

因为此前的缓存已经被清空, 所以, 客户端发送的请求报文中的Cookie信息, 在10.0.0.82上是没有的, 因此, 82会生成一个新的Session给客户端

图片.png

而82会把生成的Session分别复制给两个memcached节点, 然后把本地的删除, n2表示此Session优先备份到了n2上, n2是其主session服务器

[02:31:50 root@nginx ~]#python3 showmemcached.py 
------------------------------
[('10.0.0.82:11211 (1)', {'bak:717709018054AE60F5EFBB82BB008286-n2.Tomcat1': '[97 b; 1616527975 s]'}), ('10.0.0.83:11211 (1)', {'717709018054AE60F5EFBB82BB008286-n2.Tomcat1': '[97 b; 1616527975 s]'})]
  • Windows第二次访问

session信息不变

请求报文会携带上次响应报文中新的sessionid, 而响应报文不会再有cookie

图片.png

7.4.4 故障转移

tomcat会记录每个sessionid的主session服务器, 和备份session服务器. 正常情况, tomcat会到主session服务器查询session, 一旦主session服务器宕机, 就去备份session服务器查session

non-sticky模式和sticky模式一样. 每个session都是有两份, 因此 即使一个session服务器宕机, 也不会丢失session

7.5 redis实现non-sticky模式

图片.png
  1. 停止10.0.0.82和10.0.0.83的memcached服务
systemctl stop memcached
  1. 修改tomcat的context.xml文件

t1和t2配置相同

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:t1.tomcat.org:11211,n2:t2.tomcat.org:11211"          # 删除这行                                                                                                        
    memcachedNodes="redis://10.0.0.84:6379"
    sticky="false"
    sessionBackupAsync="false"
    lockingMode="uriPattern:/path1|/path2"    
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />
</Context>

# 将jedis包传到lib目录下

[02:55:26 root@t2 ~]#ll /usr/local/tomcat/lib | grep jedis
-rw-r--r-- 1 root   root    586620 Aug 22  2020 jedis-3.0.0.jar

systemctl restart tomcat
  1. 10.0.0.84 安装redis
[02:50:01 root@redis ~]#yum -y install redis
bind 0.0.0.0  # 修改bind地址
systemctl enable --now redis

  1. 查看tomcat日志, 验证配置成功
-  finished initialization:
- sticky: false
- operation timeout: 1000
- node ids: []
- failover node ids: []
- storage key prefix: null
- locking mode: uriPattern:/path1|/path2 (expiration: 5s)

  1. 访问测试

轮训调度, sessionid不变

图片.png
图片.png
[02:57:27 root@redis ~]#redis-cli
127.0.0.1:6379> keys *
1) "C2E86AED25FF6F08B1B679E1B158ADD4.Tomcat1"
2) "validity:7AEA93A52E10ED345E5C7EE57B5F6D0E.Tomcat2"

7.6 Session解决方案对比

session绑定,基于IP或session cookie的。其部署简单,尤其基于session黏性的方式,粒度小,对负载均衡影响小。但一旦后端服务器有故障,其上的session丢失。
session复制集群,基于tomcat实现多个服务器内共享同步所有session。此方法可以保证任意一台后端服务器故障,其余各服务器上还都存有全部session,对业务无影响。但是它基于多播实现心跳,TCP单播实现复制,当设备节点过多,这种复制机制不是很好的解决方案。且并发连接多的时候,单机上的所有session占据的内存空间非常巨大,甚至耗尽内存。
session服务器,将所有的session存储到一个共享的内存空间中,使用多个冗余节点保存session,这样做到session存储服务器的高可用,且占据业务服务器内存较小。是一种比较好的解决session持久的解决方案。

以上的方法都有其适用性。生产环境中,应根据实际需要合理选择。

不过以上这些方法都是在内存中实现了session的保持,可以使用数据库或者文件系统,把session数据存储起来,持久化。这样服务器重启后,也可以重新恢复session数据。不过session数据是有时效性的,是否需要这样做,视情况而定。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容