Tomcat Session集群

参考文档:https://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<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"
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>

配置说明

  • Cluster集群配置
  • Manager会话管理器配置
  • Channel信道配置
    • Membership成员判定。使用什么多播地址、端口多少、间隔时长ms、超时时长ms。同一
      个多播地址和端口认为同属一个组。使用时修改这个多播地址,以防冲突
    • Receiver接收器,多线程接收多个其他节点的心跳、会话信息。默认会从4000到4100依次
      尝试可用端口。
      • address=”auto”, auto可能绑定到127.0.0.1上,所以一定要改为可以用的IP上去
    • Sender多线程发送器,内部使用了tcp连接池。
    • Interceptor拦截器
  • Valve
    • ReplicationValve检测哪些请求需要检测Session,Session数据是否有了变化,需要启动复
      制过程
  • ClusterListener
    • ClusterSessionListener集群session侦听器

使用<cluster className="org.apache.catalina.ha.tcp.simpleTcpCluster" / >

添加到<Engine>所有虚拟主机都可以启用Session复制

添加到<Host>,该虚拟主机可以启用Session复制

最后,在应用程序内部启用了才可以使用

前提:

  • 时间同步,确保NTP或Chrony服务正常运行。systemctl status chronyd
  • 防火墙规则。systemctl stop firewalld
IP 主机名 服务
192.168.142.151 t0 调度器 Nginx、Httpd
192.168.142.152 t1 tomcat1 JDK8、Tomcat8
192.168.142.153 t2 tomcat2 JDK8、Tomcat8

本次把多播复制的配置放到缺省虚拟主机里面,即Host之下。

特别注意修改Receiver的address属性为一个本机可对外的IP地址。

t2的server.xml中,如下

1
2
3
4
5
6
7
8
<Host name="t1.kinmfer.com" appBase="/data/webapps" autoDeploy="true" >
其他略去
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="192.168.142.152"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>

t3的server.xml中,如下

1
2
3
4
5
6
7
8
<Host name="t2.kinmfer.com" appBase="/data/webapps" autoDeploy="true" >
其他略去
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="192.168.142.153"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>

Tomcat重启后,ss命令能看到tomcat监听在4000端口上

尝试使用刚才配置过得负载均衡(移除Session黏性),测试发现Session还是变来变去。

准备web.xml

在应用中增加WEB-INF,从全局复制一个web.xml过来

1
cp /usr/local/tomcat/conf/web.xml /data/webapps/ROOT/WEB-INF/

为web.xml的<web-app>标签增加子标签<distributable/>来开启该应用程序的分布式。

重启全部Tomcat,通过负载均衡调度到不同节点,返回的SessionID不变了。

NoSQL

NoSQL是对非SQL、非传统关系型数据库的统称。
NoSQL一词诞生于1998年,2009年这个词汇被再次提出指非关系型、分布式、不提供ACID的数据库设
计模式。
随着互联网时代的到来,数据爆发式增长,数据库技术发展日新月异,要适应新的业务需求。
随着移动互联网、物联网的到来,大数据的技术中NoSQL也同样重要。
https://db-engines.com/en/ranking

分类

  • Key-value Store
    • redis、memcached
  • Document Store
    • mongodb、CouchDB
  • Column Store列存数据库,Column-Oriented DB
    • HBase、Cassandra
  • Graph DB
    • Neo4j
  • Time Series时序数据库
    • InfluxDB

Memcached

Memcached只支持能序列化的数据类型,不支持持久化,基于Key-Value的内存缓存系统。

内存分配机制

应用程序运行需要使用内存存储数据,但对于一个缓存系统来说,申请内存、释放内存将十分频繁,非
常容易导致大量内存碎片,最后导致无连续可用内存可用。

Memcached采用了Slab Allocator机制来分配、管理内存。

  • Page:分配给Slab的内存空间,默认为1MB,分配后就得到一个Slab。Slab分配之后内存按照固
    定字节大小等分成chunk。
  • Chunk:用于缓存记录kv值的内存空间。Memcached会根据数据大小选择存到哪一个chunk中,
    假设chunk有128bytes、64bytes,数据只有100bytes存储在128bytes中,存在些浪费。
    • Chunk最大就是Page的大小,即一个Page中就一个Chunk
  • Slab Class: Slab按照大小分组,就组成不同的Slab Class

懒过期Lazy Expiration

memcached不会监视数据是否过期,而是在取数据时才看是否过期,过期的把数据有效期限标识为0
并不清除该数据。以后可以覆盖该位置存储其它数据。

LRU

当内存不足时,memcached会使用LRU (Least Recently Used)机制来查找可用空间,分配给新纪录使用。

集群

Memcached集群,称为基于客户端的分布式集群。

Memcached集群内部并不互相通信,一切都需要客户端连接到Memcached服务器后自行组织这些节点,并决定数据存储的节点。

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
dnf install -y memcached

rpm -ql memcached
/etc/sysconfig/memcached
/usr/bin/memcached
/usr/bin/memcached-tool
/usr/lib/systemd/system/memcached.service
/usr/share/doc/memcached-1.4.15
/usr/share/doc/memcached-1.4.15/AUTHORS
/usr/share/doc/memcached-1.4.15/CONTRIBUTORS
/usr/share/doc/memcached-1.4.15/COPYING
/usr/share/doc/memcached-1.4.15/ChangeLog
/usr/share/doc/memcached-1.4.15/NEWS
/usr/share/doc/memcached-1.4.15/README.md
/usr/share/doc/memcached-1.4.15/protocol.txt
/usr/share/doc/memcached-1.4.15/readme.txt
/usr/share/doc/memcached-1.4.15/threads.txt
/usr/share/man/man1/memcached-tool.1.gz
/usr/share/man/man1/memcached.1.gz

cat /usr/lib/systemd/system/memcached.service
[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/memcached
ExecStart=/usr/bin/memcached -u $USER-p $PORT -m $CACHESIZE-c $MAXCONN $OPTIONS

cat /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""

前台显示看看效果
memcached -u memcached -p 11211 -f 1.25 -vv

systemctl start memcached

修改memcached运行参数,可以使用下面的选项修改/etc/sysconfig/memcached文件

  • -u username memcached运行的用户身份,必须普通用户
  • -p 绑定的端口,默认11211
  • -m num最大内存,单位MB,默认64MB
  • -c num最大连接数,缺省1024
  • -d 守护进程方式运行
  • -f 增长因子Growth Factor,默认1.25
  • -v 详细信息,-vv能看到详细信息
  • -M 内存耗尽,不许LRU
  • -U 设置UDP监听端口,0表示禁用UDP

session共享服务器

msm

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

目前项目托管在Github,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)

安装

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。

1
2
3
4
5
6
7
8
9
10
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

sticky模式

原理

当请求结束时Tomcat的session会送给memcached备份。即Tomcat session为session,
memcached session为session,使用memcached相当于备份了一份Session。

查询Session时Tomcat会优先使用自己内存的Session,Tomcat通过jvmRoute发现不是自己的
Session,便从memcached中找到该Session,更新本机Session,请求完成后更新memcached。

部署

1
2
3
4
5
<t1>   <t2>
. \ / .
. X .
. / \ .
<m1> <m2>

t1和m1部署在一台主机上,t2和m2部署在同一台。

配置

放到$CATALINA_HOME/conf/context.xml

特别注意,t1配置中为failoverNodes=”n1”,t2配置为failoverNodes=”n2”

1
2
3
4
5
6
7
8
9
以下是sticky的配置
<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.142.152:11211,n2:192.168.142.153:11211"
failoverNodes="n1"
requestUriIgnorePattern=".*\.(ico|pnglgifljpg|cssljs)$"
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中看到下面的内容

1
2
3
4
5
6
7
8
9
信息 [t1.kinmfer.com-startStop-1]
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)

配置成功后,网页访问以下,页面中看到了Session。然后运行下面的Python程序,就可以看到是否存储到了memcached中了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import memcache # pip install python-memcached

mc = memcache.client([ '192.168.142.152:11211''192.168.142.153: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)
for x in mc.get_stats( ' cachedump 5 0' ):# stats cachedump 5 0#5和上面的items返回的值有关;0表示全部
print(×)

t1、t2、n1、n2依次启动成功,分别使用http://t1.kinmfer.com:8080/和http://t2.kinmfer.com:8080/观察。
看起负载均衡调度器,通过http://t0.kinmfer.com来访问看看效果

1
2
3
4
5
6
7
8
9
10
on tomcats
192.168.142.153:8080
sessionID = 2A19B1EB6D9649C9FED3E7277FDFD478-n2.Tomcat1
wed Jun 26 16:32:11 CST 2020

on tomcats
192.168.142.152:8080
sessionID = 2A19B1EB6D9649C9FED3E7277FDFD478-n1.Tomcat2
wed Jun 26 16:32:36 CST 2020

可以看到浏览器端被调度到不同Tomcat上,但是都获得了同样的SessionID。

non-sticky模式

原理

从msm 1.4.0之后开始支持non-sticky模式。

Tomcat session为中转Session,如果n1为主session,n2为备session。产生的新的Session会发送给主、备memcached,并清除本地Session。

n1下线,n2转正。n1再次上线,n2依然是主Session存储节点。

memcached配置

放到$CATALINA_HOME/conf/context.xml

1
2
3
4
5
6
7
8
9
10
11
以下是non-sticky的配置
<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.142.152:11211,n2:192.168.142.153:11211"
sticky="false"
sessionBackupAsync="false"
lockingMode="uriPattern:/path1|/path2"
requestUriIgnorePattern=".*\.(ico|pnglgifljpglcssljs)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"/>
</Context>

redis配置

下载jedis.jar,放到$CATALINA_HOME/lib/,对应本次安装就是/usr/local/tomcat/lib。

1
2
3
4
5
6
dnf install redis -y

vim /etc/redis.conf
bind 8.8.8.0

systemctl start redis

放到$CATALINA_HOME/conf/context.xml

1
2
3
4
5
6
7
8
9
10
11
以下是non-sticky的配置
<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="redis://192.168.142.152:6379"
sticky="false"
sessionBackupAsync="false"
lockingMode="uriPattern:/path1|/path2"
requestUriIgnorePattern=".*\.(ico|pnglgifljpglcssljs)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"/>
</Context>

Session粘性总结

通过多组实验,使用不同技术实现了session持久机制

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

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

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

安全

配置安全

  1. 删除webapps目录下的所有文件,禁用tomcat管理界面;

  2. 注释或删除tomcat-users.xml文件内的所有用户权限;

  3. 更改关闭tomcat指令或禁用;

    Tomcat的server.xml中定义了可以直接关闭 Tomcat 实例的管理端口(默认8005)。可以通过 telnet 连接上该端口之后,输入 SHUTDOWN (此为默认关闭指令)即可关闭Tomcat 实例(注意,此时虽然实例关闭了,但是进程还是存在的)。由于默认关闭Tomcat 的端口和指令都很简单。默认端口为8005,指令为SHUTDOWN。

    方案一:

    1
    2
    更改端口号和指令:
    <Server port="8456" shutdown="itcast_shut">

    方案二:

    1
    2
    禁用8005端口:
    <Server port="‐1" shutdown="SHUTDOWN">
  4. 定义错误页面

    在webapps/ROOT目录下定义错误页面 404.html,500.html;
    然后在tomcat/conf/web.xml中进行配置 , 配置错误页面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <error‐page>
    <error‐code>404</error‐code>
    <location>/404.html</location>
    </error‐page>

    <error‐page>
    <error‐code>500</error‐code>
    <location>/500.html</location>
    </error‐page>

    这样配置之后,用户在访问资源时出现404,500这样的异常,就能看到我们自定义的错误
    页面,而不会看到异常的堆栈信息,提高了用户体验,也保障了服务的安全性。

应用安全

在大部分的Web应用中,特别是一些后台应用系统,都会实现自己的安全管理模块(权限模块),用于控制应用系统的安全访问,基本包含两个部分:认证(登录/单点登录)和授权(功能权限、数据权限)两个部分。对于当前的业务系统,可以自己做一套适用于自己业务系统的权限模块,也有很多的应用系统直接使用一些功能完善的安全框架,将其集成到我们的web应用中,如:SpringSecurity、Apache Shiro等。

传输安全

Tomcat支持HTTPS

  1. 生成密钥库文件

    1
    keytool -genkeypair -alias [user]  -keyalg [认证类型] -keystore [file]

    输入对应的密钥库密码, 秘钥密码等信息之后,会在当前文件夹中出现一个秘钥库文件:tomcatkey.keystore

  2. 将秘钥库文件 tomcatkey.keystore 复制到tomcat/conf 目录下。

  3. 配置tomcat/conf/server.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <Connector port="8443"
    protocol="org.apache.coyote.http11.Http11NioProtocol"
    maxThreads="150" schema="https" secure="true" SSLEnabled="true">
    <SSLHostConfig certificateVerification="false">
    <Certificate
    certificateKeystoreFile="xxx/tomcatkey.keystore"
    certificateKeystorePassword="itcast" type="RSA" />
    </SSLHostConfig>
    </Connector>
  4. 访问Tomcat,使用https协议