首页 > 技术文章 > 实现tomcat及Memcached、redis缓存服务器session共享

struggle-1216 2020-01-17 15:32 原文

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不支持集群,只支持字符串类型、字节流,不支持持久化,不支持buffer缓存,而Redis支持字符串、源地址hash等类型,支持buffer缓存,且支持主从复制、集群、支持持久化等  

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

内存分配机制

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

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

(2)Page:分配给Slab的内存空间,默认为1MB,分配后就得到一个Slab。Slab分配之后内存按照固定字节大小等分成chunk。

(3)Chunk:用于缓存记录kv值的内存空间。Memcached会根据数据大小选择存到哪一个chunk中,假设chunk有128bytes、64bytes,数据只有100bytes存储在128bytes中,存在些浪费。

        Chunk最大就是Page的大小,即一个Page中就一个Chunk

(5)Slab Class:Slab按照大小分组,就组成不同的Slab Class

 

如果有100bytes要存,那么Memcached会选择上图中Slab Class 2存储,因为它是120bytes的Chunk。

Slab之间的差异可以使用Growth Factor控制,默认1.25。

懒过期Lazy Expiration

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

LRU

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

集群

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

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

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共享存储实现LAMT的会话保持(sticky模式生产中常用)

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) # redis的jar包

官网下载依赖的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。

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
jedis-3.0.0.jar  # redis相关的jar包

sticky模式  

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

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

<t1> <t2>
 . \ / .
  . X .
 . / \ .
<m1> <m2>

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

开始部署session共享会话保持

1、在t0和t1安装memcached服务

 #yum install memcached  -y

2、修改t0和t1的配置文件,需要监听其他地址的IP地址,否则只会监听本地的IP地址,内存可以设置为1024,最大连接设置为2048

[root@centos-7 yum.repos.d]# cat /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="2048"  #修改最大连接数
CACHESIZE="1024"  # 修改内存大小
OPTIONS=""

3、启动memcached服务,监听的是11211端口

# systemctl start memcached

4、将依赖的包在官网下载下来并导入到/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
jedis-3.0.0.jar

5、修改tomcat服务的配置文件

特别注意,t0配置中为failoverNodes="n1(memcached1)", t1(tomcat1)配置为failoverNodes="n2(memcached2)"  

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

修改t0的配置文件

[root@mysql1 lib]# vim /usr/local/tomcat/conf/context.xml 

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
        memcachedNodes="n1:192.168.7.100:11211,n2:192.168.7.101:11211"
        failoverNodes="n1"  #改为n1时,n1为备用节点,优先使用n2的主机节点
        requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
        transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
        />

修改t1的配置文件

[root@openstack-2 lib]# vim /usr/local/tomcat/conf/context.xml 

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
        memcachedNodes="n1:192.168.7.100:11211,n2:192.168.7.101:11211"
        failoverNodes="n2" #改为n2时,优先使用n1的主节点,n2为备用节点。 
        requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
        transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
        />

6、修改tomcat(0和1)的主配置文件

#vim  /usr/local/tomcat/conf/server.xml
 <Engine name="Catalina" defaultHost="t0.baidu.com" jvmRoute="Tomcat1">  #加上后面的名称,方便辨认是哪个主机
<Engine name="Catalina" defaultHost="t1.baidu.com" jvmRoute="Tomcat2">

7、修改两个tomcat的server.xml配置文件

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

<Engine name="Catalina" defaultHost="t1.baidu.com">
 </Host>
        <Host name="t1.baidu.com" appBase="/data/webapps" autoDeploy="true" >
 
      </Host>
    </Engine>

8、创建t0和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>

9、启动tomcat服务:

# startup.sh

修改httpd服务的配置文件

修改httpd主配置文件

 # vim  /etc/httpd/conf/httpd.conf

注释 #DocumentRoot "/var/www/html"

修改httpd的反向代理配置

[root@computer-2 ~]# cat /etc/httpd/conf.d/vhosts.conf 
<VirtualHost *:80>
	ServerName node1.baidu.com
	ProxyRequests Off
	ProxyVia On
	ProxyPreserveHost On
	ProxyPass / balancer://lbtomcats/
	ProxyPassReverse / balancer://lbtomcats/
</VirtualHost>
<Proxy balancer://lbtomcats>
BalancerMember ajp://t0.baidu.com:8009 loadfactor=1 route=Tomcat1
BalancerMember ajp://t1.baidu.com:8009 loadfactor=2 route=Tomcat2
#ProxySet stickysession=ROUTEID
</Proxy>

启动httpd服务

# systemctl start httpd

测试效果:

 第一次测试:

 

 第二次测试:

 

 下来模拟memcached的n2宕机,此时t0和t1都转移到memcached的n1上:

 宕机n2:systemctl stop memcached

 

  

 再次重启n2的memcached服务,此时Tomcat1就会又去优先到n2进行写入缓存:

 重启n2的memcached服务:systemctl  restart memcached

 

实战二:基于session共享存储实现LNMT的会话保持(sticky模式生产中常用)

1、修改nginx服务器的主配置文件 

upstream tomcats {    #在http 段配置
server t0.baidu.com:8080 weight=1;
server t1.baidu.com:8080 weight=2;
}
 
location / {    #在server 段配置
        proxy_pass http://tomcats;
}
location ~* \.(jsp|do)$ {
        proxy_pass http://tomcats;
}

只需要将httpd的配置改为nginx配置,upstream转发到后端服务器,其他的tomcat配置不变、memcached服务配置文件都在上面:

2、测试效果:  

 此时tomcat1对应的n2和tomcat2对应的n1都是一个session ID,实验成功:

 

 

实战三:基于session共享存储(memcached)实现LNMT的会话保持(non-sticky模式)

实现原理

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

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

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

注意:其他测试页面在上面已经存在,下面就不在写入。

1、开始部署,在t0和t1的/usr/local/tomcat/conf/context.xml配置文件中修改

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

2、修改nginx服务器的主配置文件 

upstream tomcats {    #在http 段配置
server t0.baidu.com:8080 weight=1;
server t1.baidu.com:8080 weight=2;
}
 
location / {    #在server 段配置
        proxy_pass http://tomcats;
}
location ~* \.(jsp|do)$ {
        proxy_pass http://tomcats;
}

 3、启动nginx服务和tomcat服务

# nginx  
# shutdown.sh
# startup.sh

 测试效果

此时可以看到,访问tomcat时,占用的是memcached的n1

模拟此时的n1(memcached)宕机,此时n2就会占用上来,由于n1和n2是同优先级的服务器,此时就算n1启动起来,也不会再占用到n1的缓存服务器上。

实战四:基于session共享存储(redis)实现LNMT的会话保持(non-sticky模式)

 1、安装并配置redis服务

#  yum install redis  -y

#  vim /etc/redis.conf    #修改redis配置
 bind 0.0.0.0   #监听本地的所有IP地址

# systemctl start  redis    #启动redis服务

2、配置tomcat服务

修改tomcat文件时,目前没有做redis主从复制及集群模式,生产中必须做,此时将IP地址都指向了一个redis的IP地址上

[root@centos-7 ~]# vim /usr/local/tomcat/conf/context.xml

    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
        memcachedNodes="redis://192.168.7.100:6379"
        sticky="false"
        sessionBackupAsync="false"
        lockingMode="uriPattern:/path1|/path2"
        requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
        transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
        />

创建t0和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>

3、修改nginx服务器的主配置文件 

upstream tomcats {    #在http 段配置
server t0.baidu.com:8080 weight=1;
server t1.baidu.com:8080 weight=2;
}
 
location / {    #在server 段配置
        proxy_pass http://tomcats;
}
location ~* \.(jsp|do)$ {
        proxy_pass http://tomcats;
}

启动nginx和tomcat服务

#  nginx  
# shutdown.sh
# startup.sh

测试效果:

两个主机的session ID都一致,实验成功:

 

总结  

通过多组实验,使用不同技术实现了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数据是有时效性的,是否需要这样做,视情况而定。

  

 

推荐阅读