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有关联(粘性关系)
特点:
session信息会分别在Tomcat和Session服务器各存一份, 保证每个Session都在Tomcat和Session各存一份
Tomcat和session服务器可以在同一台主机, 也可部署在不同的服务
工作过程:
客户端访问, 通过前端负载均衡调度到Tomcat1
Tomcat1生成Session信息并保存在本地, 同时存放一份到另一台主机上的Memcached上. 即使T1坏了, 从另一台主机的Memcached上也能拿到Session
当T1坏了, 调度器会把原来用户的请求调度到另一台T2上, T2上由于没有Session, 则会查M2上的session, M2因为是从T1复制的Session, 因此会有原来T1上的所有Session.
如果T1和M1都宕机了, 第一次被调度到T1上的用户再次访问时, 会被调度到T2上, T2上由于没有Session, 那么会去查M2, M2由于从T1拿Session因此是有用户session信息的.
但是此时, 如果有新用户访问, 那么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相关包
- 序列化相关下载
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在同一台主机
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会一直变换
下面配置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做相同设置
重启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信息的
- 从Windows浏览器发起第一次访问
可以看到第一次访问被调度到了10.0.0.82, 按照备份原则, Session信息DB73会被备份到n2上
利用python脚本验证Sessionid DB73被备份到了n2上
第二次访问时, 按照轮询机制, 会被调度上83上, 但是83上是没有之前82给用户生成的session信息的, 因此83会直接在本地的memcached-n2上拿到session信息, 然后把本地n2上的这条session信息给删了, 再把这条信息备份到自己对应的memcached-n1节点上. 抓包能看到效果.
第三次访问又被调度回82, 82之前是有该Session信息的, 因为是自己第一次生成给用户的, 因此会在Tomcat本地存放一份, 也会再存放到自己对应的n2上, 最后达到三次访问, n1和n2有相同的sessionid
测试故障 1: tomcat宕机
停止83上tomcat服务, 多次用同一个cookie访问, 此时就只有请求报文有cookie而响应报文是没有cookie的, 因为请求报文的cookie最开始就是82给发的, 因为第一次访问是被调度到了82上, 83停掉服务后, 访问也是访问到82上, 那么82就不会再响应一个cookie了, 之后无论访问几次都只有请求报文有cookie而响应报文没有
测试故障 2: memcached宕机
停止83上的memcached服务, 再次访问, 请求报文的cookie信息会是, n2-T1, 因此上次的响应报文是n2-T1, 而响应报文变成n1-T1, 因此被调度到T1上而且n1是存活的, 之后再访问, 也是只有T1响应, 也就不会再响应报文提供cookie了, 只有请求报文会携带cookie
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给客户端
而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
7.4.4 故障转移
tomcat会记录每个sessionid的主session服务器, 和备份session服务器. 正常情况, tomcat会到主session服务器查询session, 一旦主session服务器宕机, 就去备份session服务器查session
non-sticky模式和sticky模式一样. 每个session都是有两份, 因此 即使一个session服务器宕机, 也不会丢失session
7.5 redis实现non-sticky模式
- 停止10.0.0.82和10.0.0.83的memcached服务
systemctl stop memcached
- 修改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
- 10.0.0.84 安装redis
[02:50:01 root@redis ~]#yum -y install redis
bind 0.0.0.0 # 修改bind地址
systemctl enable --now redis
- 查看tomcat日志, 验证配置成功
- finished initialization:
- sticky: false
- operation timeout: 1000
- node ids: []
- failover node ids: []
- storage key prefix: null
- locking mode: uriPattern:/path1|/path2 (expiration: 5s)
- 访问测试
轮训调度, sessionid不变
[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数据是有时效性的,是否需要这样做,视情况而定。