session管理
前言
- 假设有一台 nginx 充当负载均衡;后端有两台 tomcat(tomcat1 和 tomcat2)
- 那么当 nginx 以轮询的方式调度到后端的 tomcat1 时,tomcat1 会生成 session id 并发送给客户端
- 客户端再下次访问时,会在 cookie 中携带 sessionid,而这时如果恰巧 nginx 将客户端的请求转发到了 tomcat2 上,那么 tomcat2 发现此 cookie 中的 session id 并非是自己的 进而会将其抛弃 并重新生成一个 session id 发送给客户端(tomcat1 也是如此)
- 这样以来最终的结果就是客户端不端的收到新的 session id,而旧的 session id 因为在其它 tomcat 主机上没有对应的数据,所以相关的购物车等信息将不会保持保存
session 持久化方案:
-
负载均衡处基于 cookie 或 session 进行调度
-
Tomcat session 复制集群
-
session 共享
测试环境说明
- index.jsp(测试 session 的 jsp文件)
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>lbjsptest</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>- /etc/hosts
10.0.0.8 azheng.com # nginx
10.0.0.18 t1.azheng.com # tomcat1
10.0.0.28 t2.azheng.com # tomcat2负载均衡基于 session 调度
实现前
nginx 配置
- /apps/nginx/conf/nginx.conf
...
http {
...
upstream tomcat-server {
server 10.0.0.18:8080;
server 10.0.0.28:8080;
}
server {
listen 80;
server_name azheng.com;
charset utf-8;
location / {
proxy_pass http://tomcat-server;
}
}
...实现前测试
- 以轮询方式调度 session 不会得以绑定
# curl azheng.com
...
<div>On tomcat-server</div>
<div>10.0.0.18:8080</div>
<div>SessionID = <span style="color:blue">E987819BA4D00194F8825DC61D975083</span></div>
...
# curl azheng.com
...
<div>On tomcat-server</div>
<div>10.0.0.28:8080</div>
<div>SessionID = <span style="color:blue">26A809AFA62BC6C31DB46A6661E4A578</span></div>
...实现后
nginx 配置
- /apps/nginx/conf/nginx.conf
...
http {
...
upstream tomcat-server {
hash $cookie_JSESSIONID; # 添加基于JSESSIONID调度,JSESSIONID一致 则每次都会调度到同一台主机上
server 10.0.0.18:8080;
server 10.0.0.28:8080;
}
server {
listen 80;
server_name azheng.com;
charset utf-8;
location / {
proxy_pass http://tomcat-server;
}
}
...实现后测试
# 获取 sessionid
# curl -I azheng.com
HTTP/1.1 200
Server: nginx/1.18.0
Date: Tue, 30 Aug 2022 04:52:14 GMT
Content-Type: text/html;charset=ISO-8859-1
Connection: keep-alive
Set-Cookie: JSESSIONID=4228BB49F03E4D91834D74A164EECEEB; Path=/; HttpOnly
---
# 在请求报文中添加 session
# curl -H "Cookie: JSESSIONID=4228BB49F03E4D91834D74A164EECEEB" azheng.com
...
style="color:blue">4228BB49F03E4D91834D74A164EECEEB</span></div>
Tue Aug 30 12:52:27 CST 2022
...
---
# 会话得以保持
# curl -H "Cookie: JSESSIONID=4228BB49F03E4D91834D74A164EECEEB" azheng.com
...
style="color:blue">4228BB49F03E4D91834D74A164EECEEB</span></div>
Tue Aug 30 12:54:58 CST 2022
...
# curl -H "Cookie: JSESSIONID=4228BB49F03E4D91834D74A164EECEEB" azheng.com
...
style="color:blue">4228BB49F03E4D91834D74A164EECEEB</span></div>
Tue Aug 30 12:54:59 CST 2022
...session 复制
-
Tomcat session replication cluster
-
Tomcat 官方实现了 session 的复制集群,将每个 Tomcat 进行相互的复制同步,从而保证了所有Tomcat 都有相同的 session 信息
-
在 Tomcat 主机过多的情况下占用内存较高,比较适合小环境中使用
-
官方参考文档:https://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html
session 复制集群 配置说明
- 添加到
所有虚拟主机都可以启用session复制 - 添加到
只为该虚拟主机启动session复制 - 最后在应用程序内部启用才能使用
- 生产中,可以根据实际情况对参数进行微调,如:多播地址、发送间隔时间、故障阈值时间等
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4" #指定的多播地址
port="45564" #45564/udp
frequency="500" #间隔500ms发送
dropTime="3000"/> #故障阈值3s
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto" #监听地址,此项建议修改为当前主机的IP
port="4000" #监听端口
autoBind="100" #如果端口冲突,自动绑定其他端口,范围是4000-4100
selectorTimeout="5000" #自动绑定超时时长5s
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>范例1:实现 session复制集群
实验环境
| IP | 主机名 | 服务 | 软件 |
|---|---|---|---|
| 10.0.0.8 | proxy.azheng.vip | 调度器 | nginx |
| 10.0.0.18 | t1.azheng.vip | tomcat1 | JDK8、tomcat8 |
| 10.0.0.28 | t2.azheng.vip | tomcat2 | JDK8、tomcat8 |
在所有tomcat配置
- 这里是将此段内容添加到Host段中,即只为该虚拟主机启动session复制
conf/server.xml配置
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.3"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>webapps/ROOT/WEB-INF/web.xml配置
- 此文件没有可以拷贝一份模板然后在此基础上修改
[root@tomcat-node1 ~]# vim /data/webapps/ROOT/WEB-INF/web.xml
...
<distributable/> #倒数第二行添加此段,开启程序的分布式
</web-app>测试访问
- 可以看到只有主机在变,而sessionID没有变化
[root@clicent ~]#curl -b "JSESSIONID=D003E14AB4EC8DC1270F7279817FB2EA" proxy.azheng.vip/index.jsp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>lbjsptest</title>
</head>
<body>
<div>On tomcat-server</div>
<div>10.0.0.18:8080</div>
<div>SessionID = <span style="color:blue">D003E14AB4EC8DC1270F7279817FB2EA</span></div>
Tue Jan 04 19:10:41 CST 2022
</body>
</html>
[root@clicent ~]#curl -b "JSESSIONID=D003E14AB4EC8DC1270F7279817FB2EA" proxy.azheng.vip/index.jsp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>lbjsptest</title>
</head>
<body>
<div>On tomcat-server</div>
<div>10.0.0.28:8080</div>
<div>SessionID = <span style="color:blue">D003E14AB4EC8DC1270F7279817FB2EA</span></div>
Tue Jan 04 19:10:47 CST 2022
</body>
</html>范例2:实现 session复制集群
实验环境:
- 时间同步,关闭selinux等…
| IP | 主机名 | 服务 |
|---|---|---|
| 10.0.0.8 | proxy.azheng.org | nginx |
| 10.0.0.18 | t1.azheng.org | JDK8、tomcat8 |
| 10.0.0.28 | t2.azheng.org | JDK8、tomcat8 |
Tomcat页面准备:
#t2同理
[root@t1 ~]# mkdir -p /data/webapps/ROOT/
[root@t1 ~]# vim /data/webapps/ROOT/index.jsp
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>lbjsptest</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>proxy主机配置:
- proxy开启轮询调度模式
[root@proxy ~]# yum -y install nginx
[root@proxy ~]# cat /etc/nginx/nginx.conf
location / {
proxy_pass http://tomcat-server;
}
[root@proxy ~]# vim /etc/nginx/conf.d/tomcat-server.conf
upstream tomcat-server {
server 10.0.0.18:8080 weight=1 fail_timeout=15s max_fails=3;
server 10.0.0.28:8080 weight=1 fail_timeout=15s max_fails=3;
}
[root@proxy ~]# systemctl enable --now nginx.servicetomcat主机配置:
在所有后端tomcat主机上修改conf/server.xml,将多播复制的配置放到t1.azheng.org和t2.azheng.org虚拟主机中,即host块。特别注意修改Receiver的address属性为一个可对外的IP地址。
t1配置:
[root@t1 ~]# vim /usr/local/tomcat/conf/server.xml
.......................................................
<Engine name="Catalina" defaultHost="t1.azheng.org">
.......................................................
<Host name="t1.azheng.org" appBase="/data/webapps"
unpackWARs="false" autoDeploy="false">
####################以下内容为新加段###############################
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="230.3.3.3" #指定不冲突的多播地址
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="10.0.0.18" #指定本机的ip地址
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
#########################以上内容为新加段###############################
</Host>
</Engine>
</Service>
</Server> t2配置:
可以从t1拷贝一份,主要修改虚拟机主机,和ip、多播地址等信息即可
[root@t2 ~]# vim /usr/local/tomcat/conf/server.xml
.......................................................
<Engine name="Catalina" defaultHost="t2.azheng.org">
.......................................................
<Host name="t2.azheng.org" appBase="/data/webapps"
unpackWARs="false" autoDeploy="false">
####################以下内容为新加段###############################
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="230.3.3.3"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="10.0.0.28"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
#########################以上内容为新加段###############################
</Host>
</Engine>
</Service>
</Server> 查看端口是否开启验证配置是否生效:
t2同理,主要看4000端口是否开启
[root@t1 ~]# ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 50 [::ffff:10.0.0.28]:4000 *:*
LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:*
LISTEN 0 100 *:8080 *:* 开启应用程序的分布式
修改应用的web.html文件开启该应用程序的分布式
#将源程序的目录作为模板拷贝到虚拟主机目录下,t2也同样操作
[root@t1 ~]# cp -a /usr/local/tomcat/webapps/ROOT/WEB-INF /data/webapps/ROOT/
[root@t1 ~]# tree /data/
/data/
└── webapps
└── ROOT
├── index.jsp
└── WEB-INF
└── web.xml
#修改web.xml,t2上的文件同样进行此修改,或者将t1修改完的文件拷贝到t2
#在倒数第二行中插入
[root@t1 ~]# vim /data/webapps/ROOT/WEB-INF/web.xml
[root@t1 ~]# tail -n3 /data/webapps/ROOT/WEB-INF/web.xml
</description>
<distributable/> #添加此行
</web-app>访问测试
- 在浏览器中测试访问,看是否只有tomcat主机IP变,而sessionID不变
session 共享
msm
- msm(memcached session manager)
- 提供将Tomcat的session保持到memcached或redis的程序,可以实现高可用
- 项目网站:https://github.com/magro/memcached-session-manager,支持Tomcat6、7、8、9
相关包说明:
-
tomcat的session管理类:
- memcached-session-manager-2.3.2.jar(通用包,必须装)
- memcached-session-manager-tc8-2.3.2.jar(针对tomcat包)
-
session数据的序列化、反序列化类:
-
可选,不用则使用java内置的序列化(实测不行,必须使用其他的序列化)
-
官方推荐kyro
-
在webapps中WEB-INF/lib/下
-
msm-kryo-serializer-2.3.2.jar kryo-serializers-0.43.jar kryo-3.0.3.jar minlog-1.3.1.jar reflectasm-1.11.8.jar asm-5.2.jar objenesis-2.6.jar
-
-
驱动类:
- spymemcached-2.12.3.jar(针对memcached)
- jedis-3.0.0.jar(针对redis)
redis实现
- 参考链接:https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration
tomcat配置
#往tomcat中拷贝文件,将以下文件拷贝到 tomcat端的:$CATALINA_BASE/lib/
memcached-session-manager-tc8-2.3.2.jar
memcached-session-manager-2.3.2.jar
jedis-3.0.0.jar
msm-kryo-serializer-2.3.2.jar
kryo-serializers-0.43.jar
kryo-3.0.3.jar
minlog-1.3.1.jar
reflectasm-1.11.8.jar
asm-5.2.jar
objenesis-2.6.jar
----------------------------------------------------------------------------------
#修改tomcat配置文件,在倒数第二行添加
#memcachedNodes= 根据生产环境添加
#不指定端口则默认6379
<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="redis://:password@redis.example.com:portnumber"
sticky="false"
sessionBackupAsync="false"
lockingMode="uriPattern:/path1|/path2"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
...
</Context>准备redis
- 正常选择二进制安装即可,设置安全密码等,主要配置都在tomcat上
- 另外redis为了数据安全和冗余性要开启持久化和哨兵或集群等…