Tomcat部署

Tomcat

History

起始于SUN的一个Servlet的参考实现项目Java Web Server,作者是James Duncan Davidson,后将项目贡献给了ASF。和ASF现有的项目合并,并开源成为顶级项目,官网http://tomcat.apache.org/。

Tomcat仅仅实现了Java EE规范的与Servlet、JSP相关的类库,是JavaEE不完整实现。

著名图书出版商O’Reilly约稿该项目成员,Davidson希望使用一个公猫作为封面,但是公猫已经被另一本书使用,书出版后封面是一只雪豹。《Tomcat权威指南》

1999年发布初始版本是Tomcat 3.0,实现了Servlet 2.2和JSP1.1规范。

Tomcat 4.x发布时,内建了Catalina(Servlet容器)和Jasper(JSP engine)等。

商用的有IBM WebSphere、Oracle WebLogic(原属于BEA公司)、Oracle Oc4j、Glassfish、JBoss等。

开源实现有Tomcat、Jetty、Resin。

安装

可以使用Centos7 yum源自带的安装。yum源中是Tomcat 7.0版本。安装完通过浏览器可以观察一下首页。

# yum install tomcat tomcat-admin-webapps tomcat-webapps
# systemctl start tomcat.service
# ss -tanl
LISTEN 0 100 :::8009
LISTEN 0 100 :::8080

采用Apache官网下载,下载8.x.x

# tar xf apache-tomcat-8.5.42.tar.gz -C /usr/local
# cd /usr/local
# ln -sv apache-tomcat-8.5.42/ tomcat
"tomcat" -> "apache-tomcat-8.5.42/"

# cd tomcat
# cd bin
# ./catalina.sh --help
# ./catalina.sh version
# ./catalina.sh start

# ss -tanlp
# ./catalina.sh stop
# ./startup.sh
# ./shutdown.sh

useradd -r java 建立系统账号
上例中,启动身份是root,如果使用普通用户启动可以使用

# useradd -r java
# chown -R java.java ./*
# su - java -c '/usr/local/tomcat/bin/catalina.sh start'
# ps -aux | grep tomcat

目录结构

配置文件

组件分类

顶级组件

Server,代表整个Tomcat容器

服务类组件

Service,组织Engine和Connector,里面只能包含一个Engine

连接器组件

Connector,有HTTP、HTTPS、A JP协议的连接器

容器类

Engine、Host、Context都是容器类组件,可以嵌入其它组件,内部配置如何运行应用程序。

内嵌类

可以内嵌到其他组件内,valve、logger、realm、loader、manager等。以logger举例,在不同容器组件内定义。

集群类组件

listener、cluster

Tomcat内部组成

由上述组件就构成了Tomcat,如下图

一个Server可包含多个Service,一个Engine和多个Connector组合在一起,就是一个service。多个sevice中,Engine里的Connector中所使用的端口不能重复。每个Engine里可包含多个Host,每个Host包含多个Context,每个Context包含一个Web Application。

Apache的AJP Connecto接收客户端的二进制数据,比HTTP Connector接收字符串传输要效率高,通常情况下HTTPS Connector基本不使用。

A JP(Apache Jserv protocol)是一种基于TCP的二进制通讯协议。

核心组件
  • Tomcat启动一个Server进程。可以启动多个Server,但一般只启动一个
  • 创建一个Service提供服务。可以创建多个Service,但一般也只创建一个
    • 每个Service中,是Engine和其连接器Connector的关联配置
  • 可以为这个Server提供多个连接器Connector,这些Connector使用了不同的协议,绑定了不同的端口。其作用就是处理来自客户端的不同的连接请求或响应
  • Service内部还定义了Engine,引擎才是真正的处理请求的入口,其内部定义多个虚拟主机Host
    • Engine对请求头做了分析,将请求发送给相应的虚拟主机
    • 如果没有匹配,数据就发往Engine上的defaultHost缺省虚拟主机
    • Engine上的缺省虚拟主机可以修改
  • Host定义虚拟主机,虚拟主机有name名称,通过名称匹配
  • Context定义应用程序单独的路径映射和配置
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
    <Service name="Catalina">
        <Connector port="8080" protocol="HTTP/1.1"
            connectionTimeout="20000"
            redirectPort="8443" />
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

        <Engine name="Catalina" defaultHost="localhost">
            <Host name="localhost" appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            </Host>
        </Engine>
    </Service>
</Server>
举例说明:
  • 假设来自客户的请求为:http://localhost:8080/test/index.jsp
  1. 浏览器端的请求被发送到服务端端口8080,Tomcat进程监听在此端口上。通过侦听的HTTP/1.1 Connector获得此请求。
  2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的响应
  3. Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host。
  4. Engine匹配到名为localhost的Host。即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机
  5. localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
  6. Host匹配到路径为/test的Context
  7. path=/test的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
  8. Context匹配到URL PATTERN为*.jsp 的servlet,对应于JspServlet类构造HttpServletRequest对象和
  9. HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法。
  10. Context把执行完了之后的HttpServletResponse对象返回给Host
  11. Host把HttpServletResponse对象返回给Engine
  12. Engine把HttpServletResponse对象返回给Connector
  13. Connector把HttpServletResponse对象返回给浏览器端

应用部署

根目录

Tomcat中默认网站根目录是CATALINA_BASE/webapps/
在Tomcat中部署主站应用程序和其他应用程序,和之前WEB服务程序不同。

nginx
假设在nginx中部署2个网站应用myshop、bbs,假设网站根目录是/var/www/html,那么部署可以是这样的。
myshop解压缩所有文件放到/var/www/html/目录下。
bbs的文件放在/var/www/html/bbs下。

Tomcat
Tomcat中默认网站根目录是CATALINA_BASE/webapps/
在Tomcat的webapps目录中,有个非常特殊的目录ROOT,它就是网站默认根目录
将eshop解压后的文件放到这个ROOT中。
bbs解压后文件都放在CATALINA_BASE/webapps/bbs目录下。
每一个虚拟主机的目录都可以使用appBase配置自己的站点目录,里面都可以使用ROOT目录作为主站目录。

JSP WebApp目录结构

主页配置:一般指定为index.jsp或index.html
WEB-INF/:当前WebApp的私有资源路径,通常存储当前应用使用的web.xml和context.xml配置文件
META-INF/:类似于WEB-INF
classes/:类文件,当前webapp需要的类
lib/:当前应用依赖的jar包

实验

默认情况下,/usr/local/tomcat/webapps/ROOT/下添加一个index.html文件,观察访问到了什么?
将/usr/local/tomcat/conf/web.xml中的下面标签内容(默认页),复制到/usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml中,如下
每个独立的app都可以有自己的配置文件,不配置,则使用缺省配置文件。

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1"
  metadata-complete="true">

  <display-name>Welcome to Tomcat</display-name>
  <description>
     Welcome to Tomcat
  </description>
      <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
      <welcome-file>index.htm</welcome-file>
      <welcome-file>index.html</welcome-file>
      </welcome-file-list>
</web-app>

配置修改后,观察首页变化

webapp归档格式

.war:WebApp打包
.jar:EJB类打包文件
.rar:资源适配器类打包文件
.ear:企业级WebApp打包

传统,应用开发测试后,通常打包为war格式,这种文件部署到了Tomcat的webapps下,还可以自动展开。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">

部署Deploy

部署:将webapp的源文件放置到目标目录,通过web.xml和context.xml文件中配置的路径就可以访问该webapp,通过类加载器加载其特有的类和依赖的类到JVM上。
– 自动部署Auto Deploy:Tomcat发现多了这个应用就把它加载并启动起来
– 手动部署
– 冷部署:将webapp放到指定目录,才去启动Tomcat
– 热部署:Tomcat服务不停止,需要依赖工具manager、ant脚本、tcd(tomcat client deployer)等

反部署undeploy:停止webapp的运行,并从JVM上清除已经加载的类,从Tomcat实例上卸载掉webapp
启动start:是webapp能够访问
停止stop:webapp不能访问,不能提供服务,但是JVM并不清除它

实验部分:

1、添加一个文件,test.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>jsp例子</title>
</head>
<body>
后面的内容是服务器端动态生成字符串,最后拼接在一起
<%
out.println("hello jsp");
%>
</body>
</html>

先把test.jsp放到ROOT下去,试试看,访问http://YourIP:8080/test.jsp 。
立即可以看到,这是通过路径映射找到相应的test.jsp后,转换成test_jsp.java,在编译成test_jsp.class。
cat /usr/local/tomcat/work/Catalina/localhost/ROOT/org/apache/jsp/test_jsp.java转换后的文件。

添加一个应用

模拟部署一个应用myapp

# cd
常见开发项目目录组成
[Sun Jun 30 23:06
 root@Centos7 ~]$ mkdir /projects/myapp/{WEN-INF,META-INF,classes,lib} -pv 
mkdir: created directory ‘/projects’
mkdir: created directory ‘/projects/myapp’
mkdir: created directory ‘/projects/myapp/WEN-INF’
mkdir: created directory ‘/projects/myapp/META-INF’
mkdir: created directory ‘/projects/myapp/classes’
mkdir: created directory ‘/projects/myapp/lib’

手动复制项目目录到webapps目录下去
# cp -r /projects/myapp/ /usr/local/tomcat/webapps/

使用http://172.16.36.102:8080/myapp/访问试试看

配置详解

server.xml

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
        <Host name="localhost" appBase="webapps"
          unpackWARs="true" autoDeploy="true">
        </Host>
    </Engine>
  </Service>
</Server>

<Server port="8005" shutdown="SHUTDOWN">

8005是Tomcat的管理端口,默认监听在127.0.0.1上。SHUTDOWN这个字符串接收到后就会关闭此Server。

# telnet 127.0.0.1 8005
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
SHUTDOWN

这个管理功能建议禁用,改shutdown为一串猜不出的字符串。

<Server port="8005" shutdown="44ba3c71d57f494992641b258b965f28">

 <GlobalNamingResources>
   <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
   -->
   <Resource name="UserDatabase" auth="Container"
             type="org.apache.catalina.UserDatabase"
             description="User database that can be updated and saved"
             factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
             pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>

用户认证,配置文件是conf/tomcat-users.xml。

打开tomcat-users.xml,我们需要一个角色manager-gui

<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
    <role rolename="manager-gui"/>
    <user username="wayne" password="wayne" roles="manager-gui"/>
</tomcat-users>

Tomcat启动加载后,这些内容是常驻内存的。如果配置了新的用户,需要重启Tomcat

访问manager的时候告诉403,提示中告诉去manager的context.xml中修改

文件路径/usr/local/tomcat/webapps/manager/META-INF/context.xml

<Context antiResourceLocking="false" privileged="true" >
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|172\.\d+\.\d+.\d+" />
  <Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPrevention
Filter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/>
</Context>

看正则表达式就知道是本地访问了,由于当前访问地址是172.16.x.x,可以修改正则为

allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|172\.16\.\d+.\d+"
再次测试,输入上面认证用户登陆成功。

<Service name="Catalina">
一般情况下,一个Server实例配置一个Service,name属性相当于该Service的ID。

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

连接器配置。
redirectPort,如果访问HTTPS协议,自动转向这个连接器。但大多数时候,Tomcat并不会开启HTTPS,因为Tomcat往往部署在内部,HTTPS性能较差。

<Engine name="Catalina" defaultHost="localhost">
引擎配置。
defaultHost指向内部定义某虚拟主机。缺省虚拟主机可以改动,默认localhost。

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
虚拟主机配置。
name必须是主机名,用主机名来匹配。
appBase,当期主机的网页根目录,相对于CATALINA_HOME,也可以使用绝对路径
unpackWARs是否自动解压war格式
autoDeploy 热部署,自动加载并运行应用

虚拟主机配置实验

尝试再配置一个虚拟主机,并将myapp部署到/data/webapps目录下
<Host name="node1.magedu.com" appBase="/data/webapps/" unpackWARs="True" autoDeploy="false" />

常见虚拟主机根目录
# mkdir /data/webapps -pv
mkdir:  created directory "/data"
mkdir: created directory "/data/webapps"
# cp -r ~/projects/myapp/ /data/webapps/ROOT
# pwd
/usr/local/tomcat
# bin/shutdown.sh
# bin/startup.sh

web页面热部署/data/webapps/ROOT。右侧有start,stop,reload,undeploy,其中如果只是停止的话,缓存数据不会清除,但通常情况下也不要reload,如果是一个单体架构的站,那重新读取可能需要一定的时间,反部署通常也不使用。so 没事别点任何按钮。

访问测试:

Context配置

可用于版本更新,如myappv1,myappv2,针对不同版本设置虚拟访问路径映射。

Context作用:

  • 路径映射
  • 应用独立配置,例如单独配置应用日志、单独配置应用访问控制

<Context path="/test" docBase="/data/test" reloadable="" />

path指的是访问的路径
docBase,可以是绝对路径,也可以是相对路径(相对于Host的appBase)
reloadable,true表示如果WEB-INF/classes或META-INF/lib目录下.class文件有改动,就会将WEB应用重新加载。
生成环境中,会使用false来禁用。

将/projects/myapp/下面的项目文件复制到/data/下

# cp -r ~/projects/myapp /data/myappv1
# cd /data
# # ln -sv myappv1 test

可以修改一下index.jsp好区别一下。
Tomcat的配置文件server.xml中修改如下

<Host name="www.martinhe.com"  appBase="/data/webapps"
            unpackWARs="true" autoDeploy="false">
            <Context path="/test" docBase="/data/test" reloadable="" />
</Host>

使用http://www.martinhe.com:8080/test/
注意:这里特别使用了软链接,原因就是以后版本升级,需要将软链接指向myappv2,重启Tomcat。如果新版上线后,出现问题,重新修改软链接到上一个版本的目录,并重启,就可以实现回滚。

常见部署方式:

  1. stanalone 单机tomcat部署
  2. 单机(nginx|httpd)–HTTP|AJP–》反向代理+tomcat部署
  3. 反向代理多机tomcat部署
  4. 反向代理多机多级tomcat部署

针对上述部署方式的一些说明:

动静分离,动态资源静态化(脚本动态资源生成静态页面文件),伪静态(看似静态页面,但其实是链接到动态资源去了),现在很多网站设计时,使用去session化。
1. standalone模式,Tomcat单独运行,直接接受用户的请求,不推荐。
1. 反向代理,单机运行,提供了一个Nginx作为反向代理,可以做到静态有nginx提供响应,动态jsp代理给Tomcat
1. LNMT:Linux + Nginx + MySQL + Tomcat
2. LAMT:Linux + Apache(Httpd)+ MySQL + Tomcat
1. 前置一台Nginx,给多台Tomcat实例做反向代理和负载均衡调度,Tomcat上部署的纯动态页面更适合
1. LNMT:Linux + Nginx + MySQL + Tomcat
1. 多级代理
1. LNNMT:Linux + Nginx + Nginx + MySQL + Tomcat

Nginx和Tomcat操作演练

nginx安装:

从epel源安装nginx

# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
# yum install nginx -y
# cd /etc/nginx
# vim nginx.conf
# nginx -t

全部反向代理测试

需要两台服务器,一台安装Nginx,另一台安装tomcat。

Nginx配置文件:

    server_name  t0.martinhe.com;
    location / {
        proxy_pass http://www.martinhe.com:8080;
    }

tomcat配置文件:

<Engine name="Catalina" defaultHost="localhost">
 <Host name="www.martinhe.com"  appBase="/data/webapps"
            unpackWARs="true" autoDeploy="false">
 </Host>
</Engine>

window本地hosts文件,在最后添加一行地址解析;
172.16.36.132 t0.martinhe.com

测试访问:

http://172.16.36.132/
http://t0.martinhe.com/

动静分离代理:

Nginx配置文件:

    server_name  t0.martinhe.com;
    location / {
        root /data/htdocs;  #nginx本机提供静态页面服务
        index index.html;
    }
    location ~* \.(jsp|do)$ {
        proxy_pass http://www.martinhe.com:8080;    #动态页面发送给tomcat处理
    }

/data/htdocs目录下增加一个index.html。

[root@haproxy1 ~]# cat /data/htdocs/index.html
<h1>/data/htdocs/<font color=#FF0000>index.html</font></h1>

测试访问:

但是实际上Tomcat不太适合做动静分离,它的管理程序的图片不好做动静分离部署,若动静分离后,显示效果只剩下文字。

应用管理

全部反向代理

location / {
    proxy_pass http://172.16.36.102:8080; # 不管什么请求,都会访问后面的tomcat虚拟主机
}

点击Tomcat首页的右上角的“Manager App”按钮,弹出登录对话框。

管理界面

Applications 应用程序管理,可以启动、停止、重加载、反部署、清理过期session
Deploy 可以热部署,也可以部署war文件。

Host Manager虚拟主机管理

配置如下

[Wed Jul 03 15:00
 root@Centos7 ~]$ vim /usr/local/tomcat/conf/tomcat-users.xml
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
    <role rolename="manager-gui"/>
    <role rolename="admin-gui"/>
    <user username="martinhe" password="martinhe" roles="manager-gui,admin-gui"/>
</tomcat-users>

重启Tomcat,点击“Host Manager”按钮,可以新增虚拟主机。

httpd和Tomcat实践

# yum install httpd -y
# httpd -M
# httpd -M | grep proxy
proxy_module (shared)
proxy_ajp_module (shared)
proxy_balancer_module (shared)
proxy_http_module (shared)

****httpd配置****

proxy_http_module模块代理配置

# vim /etc/httpd/conf.d/vhosts.conf
<VirtualHost *:80>
ServerName www.martinhe.com
ProxyRequests Off   #Off关闭正向代理。
ProxyVia On     #代理的请求响应时提供一个response的via首部
ProxyPreserveHost On    #On开启。让代理保留原请求的Host首部
ProxyPass / http://172.16.36.102:8080/  #反向代理指令
ProxyPassReverse / http://172.16.36.102:8080/   #保留代理的response头不重写(个别除外)
</VirtualHost>

测试访问:

http://172.16.36.132/ #显示tomcat默认首页
http://www.martinhe.com/ #显示自定义index.html
– This is 172.16.36.102:/data/webapps/ROOT/index.html

http://www.martinhe.com/index.jsp #显示自定义页面index.jsp
– 后面的内容是服务器端动态生成字符串,最后拼接在一起 Hello! Welcom to myapp .jsp

访问到的页面显示不相同,说明ProxyPreserveHost On起了作用。

当配置ProxyPreserveHost off时,通过域名访问时,仅能访问到tomcat默认首页。可通过修改tomcat配置文件,<Engine name="Catalina" defaultHost="www.martinhe.com">,通过IP或者域名访问就可以转发到自己设置的webapps目录里的ROOT内网页。

proxy_ajp_module模块代理配置

<VirtualHost *:80>
ServerName www.martinhe.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / ajp://172.16.36.102:8009/
</VirtualHost>

查看Server Status可以看到确实使用的是ajp连接了。

负载均衡

动态服务器的瓶颈往往并发能力太弱,往往需要多台动态服务器同时提供服务,将并发分摊到每台动态服务器,就需要调度,采用适当的调度策略和算法,This is called LB(Load balance)负载均衡。

当单机tomcat服务能力不够时,出现后续的多机多级部署,凸显出的问题就是session。由于淡出设计HTTP协议未能考虑到未来的发展。

HTTP的无状态,有连接和短连接

  1. 无连接:即同一个浏览器间隔三秒访问同一个网站,服务器提供端不会记录用户是否访问过。后来通过cookie和session机制来判断。
    1. 浏览器端第一次HTTP请求服务器端时,在服务器端使用session这种技术,就可以在服务器端产生一个随机值即SessionID发给浏览器端,浏览器端收到后会保持这个SessionID在Cookie当中,这个Cookie值不能持久存储,浏览器关闭就消失。浏览器在每一次提交HTTP请求的时候会把这个SessionID传给服务器端,服务器端就可以通过比对知道是谁了
    2. Session通常会保存在服务器端内存中,如果没有持久化,则易丢失
    3. Session会定时过期。过期后浏览器如果再访问,服务端发现没有此ID,将重新获得新的SessionID
    4. 更换浏览器也将重新获得新的SessionID
  2. 有连接:是因为它基于TCP协议,是面向连接的,需要3次握手、4次断开。即TCP三次握手四次挥手。
  3. 短连接:Http 1.1之前,都是一个请求一个连接,而Tcp的连接创建销毁成本高,对服务器有很大的影响。所以,自Http 1.1开始,支持keep-alive,默认也开启,一个连接打开后,会保持一段时间(可设置),浏览器再访问该服务器就使用这个Tcp连接,减轻了服务器压力,提高了效率。

服务器端如果故障,即使Session被持久化了,但是服务没有恢复前都不能使用这些SessionID。

如果使用HAProxy或者Nginx等做负载均衡器,调度到了不同的Tomcat上,那么也会出现找不到SessionID的情况。

会话保持方式

1. session sticky会话黏性

Session绑定
—–nginx:source ip
—–HAProxy:cookie
优点:简单易配置
缺点:如果目标服务器故障后,如果没有做sessoin持久化,就会丢失session

2. session复制集群

Tomcat自己的提供的多播集群,通过多播将任何一台的session同步到其它节点。
缺点:
Tomcat的同步节点不宜过多,互相及时通信同步session需要太多带宽
每一台都拥有全部session,内存损耗太多

3. session server

session 共享服务器,使用memcached、redis做共享的Session服务器。

规划设计:

IP地址———– 主机名—- 服务——- 安装包———–
172.16.36.132 haproxy1 调度器— Nginx,HTTPD
172.16.36.102 Centos7– tomcat1 JDK8,TOMCAT8
172.16.36.112 node2—- tomcat2 JDK8,TOMCAT8

每台主机的域名解析
172.16.36.132 t0.martinhe.com t0
172.16.36.102 t1.martinhe.com t1
172.16.36.112 t2.martinhe.com t2

环境变量配置

# vim /etc/profile.d/tomcat.sh
export CATALINA_HOME=/usr/local/tomcat
export PATH=$CATALINA_HOME/bin:$PATH

项目路径配置

# mkdir -pv /data/webapps/ROOT
编写测试jsp文件,内容在下面
# vim /data/webapps/ROOT/index.jsp
# scp -r server.xml 172.16.36.112:/usr/local/tomcat/conf

启动Tomcat服务

# startup.sh

测试用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>

t1虚拟主机配置

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

t2虚拟主机配置

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

Nginx调度

nginx配置如下

vim /etc/nginx/conf.d/upstream.sh

upstream tomcats {
        #ip_hash; # 先禁用看看轮询,之后开启开黏性
        server t1.martinhe.com:8080;
        server t2.martinhe.com:8080;
}
server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  t0.martinhe.com;
        root         /usr/share/nginx/html;

        location ~* \.(jsp|do)$ {
        proxy_pass http://tomcats;
       }
}

测试http://t0.martinhe.com/index.jsp,可以看到轮询调度效果。

刷新后

在upstream中使用ip_hash指令,使用客户端IP地址Hash。这个hash值使用IPv4地址的前24位或全部的IPv6地址。

配置完reload nginx服务。测试一下看看效果。关闭Session对应的Tomcat服务,再重启启动它,看看Session的变化。

关闭112这台服务器tomcat服务后,可看到自动切换到102服务器,sessionID变更:

重新开启112这台服务器tomcat服务后,可看到又自动切换到112服务器,sessionID变更:

Httpd调度

关闭httpd默认主机

# cd /etc/httpd/conf
# vim httpd.conf
注释 #DocumentRoot "/var/www/html"
# cd ../conf.d
# vim vhosts.conf
# httpd -t
# systemctl start httpd

负载均衡配置说明

配置代理到balancer
ProxyPass [path] !|url [key=value [key=value ...]]
Balancer成员
BalancerMember [balancerurl] url [key=value [key=value ...]]
设置Balancer或参数
ProxySet url key=value [key=value ...]

ProxyPass和BalancerMember指令参数

Balancer参数

RroxySet指令也可以使用上面的参数。

在tomcat的配置中Engine使用jvmRoute属性,可便于观察调度。

t1、t2的tomcat配置中分别增加jvmRoute
<Engine name="Catalina" defaultHost="t1.magedu.com" jvmRoute="Tomcat1">
<Engine name="Catalina" defaultHost="t2.magedu.com" jvmRoute="Tomcat2">

conf.d/vhosts.conf内容如下

[root@haproxy1 ~]# vim /etc/httpd/conf.d/vhosts.conf
<VirtualHost *:80>
ServerName t0.martinhe.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
</VirtualHost>
<Proxy balancer://lbtomcats>
BalancerMember http://t1.martinhe.com:8080 loadfactor=1
BalancerMember http://t2.martinhe.com:8080 loadfactor=2
</Proxy>

loadfactor设置为1:2,便于观察。观察调度的结果是轮询的,轮询的不是很规律。

使用session黏性

修改conf.d/vhosts.conf

Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED

<VirtualHost *:80>
    ServerName t0.martinhe.com
    ProxyRequests Off
    ProxyVia On
    ProxyPreserveHost On
    ProxyPass / balancer://lbtomcats/
    ProxyPassReverse / balancer://lbtomcats/
</VirtualHost>

<Proxy balancer://lbtomcats>
    BalancerMember http://t1.martinhe.com:8080 loadfactor=1 route=Tomcat1
    BalancerMember http://t2.martinhe.com:8080 loadfactor=2 route=Tomcat2
    ProxySet stickysession=ROUTEID
</Proxy>

多次刷新浏览器,发现Session不变了,一直找的同一个Tomcat服务器。

ajp调度

修改conf.d/vhosts.conf,注意ajp修改对应端口

[root@haproxy1 ~]# vim /etc/httpd/conf.d/vhosts.conf 
<VirtualHost *:80>
ServerName t0.martinhe.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
</VirtualHost>

<Proxy balancer://lbtomcats>
BalancerMember ajp://t1.martinhe.com:8009 loadfactor=1 route=Tomcat1
BalancerMember ajp://t2.martinhe.com:8009 loadfactor=2 route=Tomcat2
ProxySet stickysession=ROUTEID
</Proxy>

ProxySet stickysession=ROUTEID 开启后,发现Session不变了,一直找的同一个Tomcat服务器。

虽然,上面的做法实现客户端在一段时间内找同一台Tomcat,从而避免切换后导致的Session丢失。但是如果Tomcat节点挂掉,那么Session依旧丢失。

假设有A、B两个节点,都将Session持久化。如果Tomcat A服务下线期间用户切换到了Tomcat B上,就获得了Tomcat B的Session,就算持久化Session的Tomcat A上线了,也没用了。

Tomcat Session集群

参考:https://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html

<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.100.100.8"
            port="45564"
            frequency="500"
            dropTime="3000"/>
    <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
            address="192.168.1.7"   #此处写有路由出口的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>

配置说明

  • 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侦听器

使用

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

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

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

前提:
  • 时间同步,确保NTP或Chrony服务正常运行。# systemctl status chronyd
  • 防火墙规则。# systemctl stop firewalld

规划设计同上述实验:

IP地址———– 主机名—- 服务——- 安装包———–
172.16.36.132 haproxy1 调度器— Nginx,HTTPD
172.16.36.102 Centos7– tomcat1 JDK8,TOMCAT8
172.16.36.112 node2—- tomcat2 JDK8,TOMCAT8

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

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

t1的server.xml中,如下

      <Host name="t1.martinhe.com"  appBase="/data/webapps"
            unpackWARs="true" autoDeploy="false">
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
            address="192.168.1.7"
            port="4000"
            autoBind="100"
            selectorTimeout="5000"
            maxThreads="6"/>
    </Host>

t2的server.xml中,如下

      <Host name="t2.martinhe.com"  appBase="/data/webapps"
            unpackWARs="true" autoDeploy="false">
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
            address="192.168.1.6"
            port="4000"
            autoBind="100"
            selectorTimeout="5000"
            maxThreads="6"/>
    </Host>

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

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

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

为web.xml的 标签增加子标签 来开启该应用程序的分布式。

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1"
  metadata-complete="true">

  <display-name>Welcome to Tomcat</display-name>
  <description>
     Welcome to Tomcat
  </description>
      <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
      <welcome-file>index.html</welcome-file>
      <welcome-file>index.htm</welcome-file>
      </welcome-file-list>
  #添加如下:
  <distributable/>
</web-app>

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

NoSQL

NoSQL是对非SQL、非传统关系型数据库的统称。

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大小。
    • Chunk最大就是Page的大小,即一个Page中就一个Chunk,即一个page里只分一个chunk
  • Slab Class:Slab按照大小分组,就组成不同的Slab Class,即相同数量的chunk,且chunk大小相同,这样的page联合组成Slab Class

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

懒过期Lazy Expiration

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

LRU算法机制

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

集群

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

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

安装Memcached

[root@node2-centos7 ~]# yum install memcached
[root@node2-centos7 ~]# 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

[root@node2-centos7 ~]# cat /usr/lib/systemd/system/memcached.service
[Unit]
Description=Memcached 
Before=httpd.service
After=network.target

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

[Install]
WantedBy=multi-user.target

[root@node2-centos7 ~]# cat /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""

前台显示看看效果
# memcached -u memcached -p 11211 -f 1.25 -vv
slab class   1: chunk size        96 perslab   10922
slab class   2: chunk size       120 perslab    8738
slab class   3: chunk size       152 perslab    6898
slab class   4: chunk size       192 perslab    5461
slab class   5: chunk size       240 perslab    4369
slab class   6: chunk size       304 perslab    3449
slab class   7: chunk size       384 perslab    2730
slab class   8: chunk size       480 perslab    2184
。。。

# memcached -u memcached -p 11211 -f 1.5 -vv
slab class   1: chunk size        96 perslab   10922
slab class   2: chunk size       144 perslab    7281
slab class   3: chunk size       216 perslab    4854
slab class   4: chunk size       328 perslab    3196
slab class   5: chunk size       496 perslab    2114
slab class   6: chunk size       744 perslab    1409
slab class   7: chunk size      1120 perslab     936
slab class   8: chunk size      1680 perslab     624
。。。


服务脚本启动
# 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
[root@node2-centos7 ~]# yum list all | grep memcached
memcached.x86_64                          1.4.15-10.el7_3.1          @development
libmemcached.i686                         1.0.16-5.el7               development
libmemcached.x86_64                       1.0.16-5.el7               development
libmemcached-devel.i686                   1.0.16-5.el7               development
libmemcached-devel.x86_64                 1.0.16-5.el7               development
memcached-devel.i686                      1.4.15-10.el7_3.1          development
memcached-devel.x86_64                    1.4.15-10.el7_3.1          development
opensips-memcached.x86_64                 1.10.5-4.el7               epelaliyun 
php-ZendFramework-Cache-Backend-Libmemcached.noarch
php-pecl-memcached.x86_64                 2.2.0-1.el7                epelaliyun 
python-memcached.noarch                   1.48-4.el7                 development
uwsgi-router-memcached.x86_64             2.0.17.1-2.el7             epelaliyun 

与memcached通信的不同语言的连接器。

libmemcached提供了C库和命令行工具。

协议

查看/usr/share/doc/memcached-1.4.15/protocol.txt

# yum install telnet
# telnet 172.16.36.102 11211
stats
add mykey 1 60 4    #flag1  存活60s 4个字节
test
STORED      #存储成功
get mykey       #获取mykey内容
VALUE mykey 1 4
test
END
set mykey 1 60 5
test1
STORED
get mykey
VALUE mykey 1 5
test1
END

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数据的序列化、反序列化类:序列化即C—》S,打包按字节发送,反序列化,将字节还原值
    • 官方推荐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。

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,则需要此项

sticky模式:企业使用,必须掌握

原理

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

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

部署

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

配置

放到 $CATALINA_HOME/conf/context.xml 中

特别注意,t1配置中为failoverNodes=”n1″, t2配置为failoverNodes=”n2″
以下是sticky的配置

<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:t1.martinhe.com:11211,n2:t2.martinhe.com:11211"
    failoverNodes="n1"
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
</Context>

memcachedNodes="n1:t1.martinhe.com:11211,n2:t2.martinhe.com:11211"

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

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

实验

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

[Thu Jul 04 12:43
 root@Centos7 /usr/local/tomcat]$ tail logs/catalina.out
- operation timeout: 1000
- node ids: [n2]
- failover node ids: [n1]
- storage key prefix: null
- locking mode: null (expiration: 5s)
.....

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

import memcache # pip3 install python3-memcached

mc = memcache.Client([
    '172.16.36.102:11211',
    '172.16.36.112: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表示全部

t1、t2、n1、n2依次启动成功,分别使用http://t1.magedu.com:8080/ 和http://t2.magedu.com:8080/ 观察。

看起负载均衡调度器,通过http://t0.magedu.com来访问看看效果

可以看到浏览器端被调度到不同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配置

<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:t1.martinhe.com:11211,n2:t2.martinhe.com:11211"
    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配置

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

# yum install redis

# vim /etc/redis.conf
bind 0.0.0.0

# systemctl start redis

放到 $CATALINA_HOME/conf/context.xml 中

<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="redis://172.16.36.112: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>

总结

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