1  概述

本文通过例子,介绍LNMT和LAMT,结合例子介绍如何实现如下的功能

(1) nginx + tomcat cluster, httpd(proxy_http_module)+tomcat cluster, httpd(proxy_ajp_module)+tomcat cluster;

(2) tomcat cluster升级为session cluster, 使用deltaManager;

(3) tomcat cluster将会话保存至memcached中;

2  概念介绍

LNMT:Linux Nginx MySQL Tomcat 

Client (http) --> nginx (reverse proxy)(http) --> tomcat  (http connector)

tomcat处理静态资源效率比nginx低,而且消耗更多的资源,所以静态资源一般有nginx作为web服务器处理

 LAMT:Linux Apache(httpd) MySQL Tomcat 

httpd的代理模块有如下三个:

    proxy_module

    proxy_http_module:适配http协议客户端;

    proxy_ajp_module:适配ajp协议客户端;

Client (http) --> httpd (proxy_http_module)(http) --> tomcat  (http connector)

Client (http) --> httpd (proxy_ajp_module)(ajp) --> tomcat  (ajp connector)

Client (http) --> httpd (mod_jk)(ajp) --> tomcat  (ajp connector)

2.1  会话保持:

动态内容通常要追踪用户的会话,php或者jsp的会话会定期保存在磁盘,所以重新上线的机器,会话可以从磁盘恢复。但是,需要解决单点故障的问题。

(1) session sticky,会话绑定

    source_ip:调度粒度粗糙,用于三四层调度

    nginx: ip_hash

    haproxy: source

    lvs: sh

cookie:七层调度,粒度精细

nginx:hash 

haproxy: cookie

(2) session cluster:delta session manager,会话集群,通过多播的方式,把会话传给集群中的其他主机,保证集群中的所有主机都拥有相同的会话信息。这种调度的缺点是,当站点访问量很大的时候,站点保存的会话会很多,在会话保持期间每一台主机持有相同的会话,导致了对每台服务器都造成压力。

(3) session server:redis(store), memcached(cache)。节点的会话都保存在服务器中,一般是kv存储,如redis(kv store,持久存储)或者memcache(kv  cache,不能持久存储),redis和memcache的存储都在内存中,没有复杂的约束关系,(不能使用mysql,因为myslq的约束多,数据存取效率低,不过mysql具有事务的强移植).session server 建议配置为冗余模型。

服务器后端冗余,这里有两种机制:

客户端存储的时候,直接把会话存储在后端的多台缓存服务器上。

另一机制,后端存储服务器自动同步数据。缺点是存在延时。后端服务可能存在故障,可以用keepalive来实现,但是节点太多的时候,keepalive就实现不了,这个时候,可以使用服务注册(服务发现+)来实现,在服务总线上注册自己主机的信息。

2.2  Tomcat Cluster(session)

有三个方式

    (1) session sticky

    (2) session cluster:tomcat delta manager

    (3) session server :借助memcached

Tomcat Cluster,有以下的方案构建集群

一个httpd调度用户请求到tomcat,httpd有多种变化方式,所以有三种方式

(1) httpd + tomcat cluster,

httpd: mod_proxy, mod_proxy_http, mod_proxy_balancer

tomcat cluster:http connector

(2) httpd + tomcat cluster

httpd: mod_proxy, mod_proxy_ajp, mod_proxy_balancer

tomcat cluster:ajp connector

(3) httpd + tomcat cluster,需要额外编译安装,现在不流行了,httpd调度目前主要使用前两种方式,以下将不演示该方法的实现

httpd: mod_jk

tomcat cluster:ajp connector,实现反代和会话保持

(4) nginx + tomcat cluster  一个nginx调度用户请求到tomcat

3  例子

3.1  环境准备

实验前首先实现时间同步和主机名解析

环境如下

host  172.18.50.75为www面向客户端,作为调度器,两种安装方法,安装nginx和httpd,这里通过两种方法演示调度器

172.18.50.72 和73,为tomcat server

安装软件; 172.18.50.72 和73

yum install java-1.8.0-openjdk-devel
yum install tomcat tomcat-lib tomcat-admin-webapps tomcat-docs-webapp tomcat-webapps

172.18.50.72 和73 建议将主机名写入hosts,如172.18.50.72  node1  ,73为node2 ;解析主机名

准备tomcat虚拟主机test和站点页面

mkdir -pv /usr/share/tomcat/webapps/test/WEB-INF
vim  /usr/share/tomcat/webapps/test/index.jsp

#页面内容如下

<%@ page language="java" %>
<html>
    <head><title>Tomcat7B</title></head>
    <body>
        <h1><font color="blue">Tomcat7B.sunny.com</font></h1>
        <table align="centre" border="1">
            <tr>
                <td>Session ID</td>
            <% session.setAttribute("sunny.com","sunny.com"); %>
                <td><%= session.getId() %></td>
            </tr>
            <tr>
                <td>Created on</td>
                <td><%= session.getCreationTime() %></td>
            </tr>
        </table>
    </body>
</html>

重启tomcat服务

systemctl restart tomcat

测试

在浏览器上测试,输入http://172.18.50.72:8080/test和 http://172.18.50.73:8080/test可以看到蓝色和红色的相关session内容,到这里环境环境准备完成

3.2  调度配置

实现调度这里介绍三种方法

调度器配置参数

BalancerMember:

格式:BalancerMember [balancerurl] url [key=value [key=value ...]]

status:

D:worker被禁用,不会接受任何请求。

S:worker被管理性的停止。

I:worker处于忽略错误模式,并将始终被视为可用。

H:worker处于热备份模式,只有在没有其他可行的worker时才能使用。

E:worker处于错误状态。

N:worker处于流失模式,只会接受发往自己的现有粘性会话,并忽略所有其他请求。

loadfactor:负载因子,即权重;

lbmethod:

  设置调度算法,负载均衡算法三种:

    byrequests 为roundrobin,

    bybusiness:根据后端的繁忙程度来调度

    bytraffic:根据流量来调度,根据流量较空闲来调度

stickysession:

平衡器粘滞的会话名称。 该值通常设置为类似于JSESSIONID或PHPSESSIONID的值,并且取决于支持会话的后端应用程序服务器。 如果后端应用程序服务器的cookie使用不同的名称和url编码的id使用不同的名称(如servlet容器),请使用| 分开他们。 第一部分是cookie,第二部分是路径。在Apache HTTP Server 2.4.4及更高版本中可用。


方法一:配置nginx,实现调度器

vim  /etc/nginx/conf.d/vhost.conf
upstream tcsrvs {
    server 172.18.50.72:8080;
    server 172.18.50.73:8080;
    }   
server {    
    location / { 
            proxy_pass http://tcsrvs;
        }   
}

重启nginx,在浏览器输入 http:172.18.50.75/test可以查看到不同内容,已经实现调度

或者用curl命令测试调度

for i in {1..10}; do curl -s http://172.18.50.75/test/index.jsp | grep -i tomcat;done

方法二:配置httpd,基于http模块实现调度

配置如下

vim  /etc/httpd/conf.d/http-tomcat.conf
<proxy balancer://tcsrvs>
    BalancerMember http://172.18.50.72:8080
    BalancerMember http://172.18.50.73:8080
    ProxySet lbmethod=byrequests
</Proxy>
<VirtualHost *:80>
    ServerName lb.sunny.com
    ProxyVia On
    ProxyRequests Off 
     ProxyPreserveHost On
 <Proxy *>
     Require all granted
 </Proxy>
     ProxyPass / balancer://tcsrvs/
     ProxyPa***everse / balancer://tcsrvs/
 <Location />
     Require all granted
 </Location>
</VirtualHost>

测试

重启http,在浏览器输入 http:172.18.50.75/test可以查看到不同内容,已经实现调度

或者用curl命令测试调度

for i in {1..10}; do curl -s http://172.18.50.75/test/index.jsp | grep -i tomcat;done

 方法三:配置httpd,基于ajp模块实现调度, 后端tomcat依靠ajp协议提供服务

vim  /etc/httpd/conf.d/ajp-tomcat.conf
<proxy balancer://tcsrvs>
    BalancerMember ajp://172.18.50.72:8009
    BalancerMember ajp://172.18.50.73:8009
    ProxySet lbmethod=byrequests
</Proxy>
 <VirtualHost *:80>
    ServerName lb.sunny.com
    ProxyVia On
    ProxyRequests Off 
     ProxyPreserveHost On
 <Proxy *>
     Require all granted
 </Proxy>
     ProxyPass / balancer://tcsrvs/
     ProxyPa***everse / balancer://tcsrvs/
 <Location />
     Require all granted
 </Location>
</VirtualHost>

测试

重启http,在浏览器输入 http:172.18.50.75/test可以查看到不同内容,已经实现调度

或者用curl命令测试调度

for i in {1..10}; do curl -s http://172.18.50.75/test/index.jsp | grep -i tomcat;done

另外,可以关闭掉后端一台主机,会发现调度器75已经不会再调度请求到对应的失败主机上,因为httpd默认有健康检查功能,移除主机速度快,但是当主机恢复正常后,即被关闭的服务器重新启动tomcat 8005主进程后,健康检查就很慎重,要一段时间后才会重新添加恢复的主机为正常

标记后端主机不可用,就不会再次调度到该台被标记位D的主机,如下例子,则将不会被调度到172.18.50.72这台机器上

<proxy balancer://tcsrvs>
    BalancerMember ajp://172.18.50.72:8009 status=D
    BalancerMember ajp://172.18.50.73:8009
    ProxySet lbmethod=byrequests
</Proxy>

httpd的balance模块

httpd的balance模块有内键的状态管理接口,可以启用 balancer-manager,内键管理接口,启用内键管理器,不要开放给任何人访问,如指定固定ip 172.18.50.99这台主机才能访问该链接。在httpd的子配置文件里添加如下的配置

<Location /balancer-manager>
    SetHandler balancer-manager  
    ProxyPass !
    Require all granted
    order deny,allow
    deny from all
    allow from 172.18.50.99     
 </Location>

测试,在浏览器里输入http://172.18.50.75/balancer-manager 查看。同时,该页面的链接部分,点击进入后可以实现相关的管理配置。

到这里,调度的方法介绍完成。

3.3  session sticky

基于cookie会话绑定,依赖调度器,需要在调度器上配置

基于ajp协议调度,当后端服务器状态不变时(基于env=BALANCER_ROUTE_CHANGED这个关键字实现),不会重新调度,同一请求会调度到同一机器上

http的配置文件,env这个选项表示当后端服务器发生变化的时候,会话绑定才会变化,否则不变

调度器75上子配置文件配置如下

Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
<proxy balancer://tcsrvs>
    BalancerMember ajp://172.18.50.72:8009 route=Tomcat7B
    BalancerMember ajp://172.18.50.73:8009 route=Tomcat7C
    ProxySet lbmethod=byrequests
    ProxySet stickysession=ROUTEID
</Proxy>

virtualhost部分配置不变,见上面调度器的配置

tomcat 72和73上配置如下,只需在engine配置段里添加jvmRoute的键值对即可

vim /usr/share/tomcat/conf/server.xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tc7b">

测试,在浏览器上输入http://172.18.50.75/test/,当后端的服务器不发生变化,则同一请求始终会被调度到同一台服务器上,得到的session和内容是一样的。

httpd基于http协议的调度方法和ajp协议基本一致。更改协议和端口即可。

注意这里http不支持基于源地址绑定。nginx可以基于源地址进行调度,这里的调度也可以使用haproxy来实现。

 这里还有另一种配置实现了会话的绑定,如通过proxypass 配置段里添加stickysession=jsessionid 来构建,但是一般不采用如下的配置,因为有可能jssesionid带有服务器本身的内容,如节点的标志,导致hash值每次不一样,不能实现会话绑定的效果,所以一般不基于这种方式来实现会话绑定,这里就不做配置,建议使用上面的例子来实现会话的绑定。

3.4  session cluster

session replication  cluster方法实现会话集群即节点会变,但是会话id不变的效果

主要在后端服务器把后端的服务器构建成会话集群,每一个节点本地获取到新的会话或者更改会话值的时候,要通过后端的会话集群信道,将会话同步到后端的所有服务器上

通过多播实现session的传输复制到每一台后端服务器上,每个收到新的会话信息时,会更新自己的会话集,所以后续调度器根据调度算法随意调度到任意主机,都保存对应的会话信息,可以处理请求,可以返回相应的信息。这里的会话一般是通过多播发送的给其他主机,即基于多播实现会话扩散。

集群有多种会话管理器

persistent manager(持久会话管理器),默认的会话管理器,会话会被同步到磁盘内,即使关机,会话依然存在。

delta manager(会话管理器)使用多播集群构建replication cluster要通过delta manager会话管理器来实现,这里可以理解为每一次传递都是通过增量传递,会话只传递更新的部分会话

backup  manager (备用会话管理器) 工作逻辑是会话的复制只在有限的主机间复制,如两台,而不是全部的主机,调度时只调度到其中一台,当这台主机异常,才会调度到另一台主机

这里演示基于Delta Manager的会话绑定,不依赖于调度器,即不需要在前端调度器上配置会话绑定的配置,只需配置调度即可。在后端tomcat上配置cluster,是app级别的,如果直接配置在engine中,则对该engine对应的所有app都有效,也可以直接配置在app中,即一般是放在在host中,只对对应的app生效。

tomcat上相关配置介绍如下

(1) 配置启用集群,将下列配置放置于<engine>或<host>中;多播地址建议不要使用默认的,节点设定为一致
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
  #建议放在对应的host段里
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
#定义manager,表示调用哪个manager,如这里使用deltamanager.可以使用会话ha功能,即会话可以同步到其他节点
<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" #绑定在本机正常通信的地址,auto自动找,不建议,绑定的地址为auto时,会自动解析本地主机名,并解析得出的IP地址作为使用的地址;建议直接设定本机ip,如172.18.50.75,
port="4000"
autoBind="100"
selectorTimeout="5000" #挑选器的超时时间
maxThreads="6"/> #最大线程数,表示一共启用多少线程来接收其他主机传过来的会话,如集群4台,这里就设置为3就足够了。
 # Receiver表示定义一个接收器,接收别人传过来的多播信息
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
# Sender是定义如何把会话信息发送给同一集群中的其他节点
<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.MessageDispatch15Interceptor"/>
#Interceptor解析器,解析当什么时候
</Channel>
# Channel是定义集群成员关系
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/> #valve阀门,考虑到jvmRoute
<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.JvmRouteSessionIDBinderListener"/>
#ClusterListener监听java集群资源是否发生变化,如果发生变化要如何变得集群的变化。
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>        
确保Engine的jvmRoute属性配置正确。
 (2) 配置webapps
编辑WEB-INF/web.xml,添加<distributable/>元素;这个要手动添加

例子

编辑tomcat主机,

步骤一,修改配置文件,有两个地方需要配置

一是engine里需要添加jvmRoute配置

二是 Cluster配置放在host配置段里,以下配置只需要调整组播地址和本机的ip,其他地方不需要调整。两台tomcat都需要配置

vim /usr/share/tomcat/conf/server.xml 
  <Engine name="Catalina" defaultHost="localhost" jvmRoute="tc7b">
#以下放在host配置段里
<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="172.18.50.72"  #调整为本机的ip,不使用默认的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.MessageDispatch15Interceptor"/>
</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.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>

步骤二

集群会话的app的web.xml里需要配置<distributable/>这个元素,配置在web.xml的web-app配置段里即可。一般程序员开发的站点web.xml这个文件肯定是存在的,而且是放在WEB-INF目录下,不过实验环境没有,所以就拷贝公共的web.xml到对应主机目录下

cp /etc/tomcat/web.xml /usr/share/tomcat/webapps/test/WEB-INF/
vim /usr/share/tomcat/webapps/test/WEB-INF/web.xml
<distributable/>

保存后退出。重启tomcat服务器

测试:在浏览器上输入http://172.18.50.75/test/,可以看到此时请求被调度到不同的机器上,但是,页面上的session id始终保持不变。则实验成功。说明在不同的服务器上已经有同一session id。即实现了会话集群即节点会变,但是会话不变。

3.5  session  server

借助第三方的工具memcached实现,tomcat cluster将会话保存至memcached中,通过memcached来实现。把memcached当做tomcat的session server,不需要在前端做绑定会话,不需要在tomcat服务器绑定会话。后端的memcache需要做高可用,后端支持两台memcache,tomcat服务器开启双写机制,所有要存储的信息都同时写入后端的两台memcache里,读取数据时可以只读取一台,当memcache主服务器挂掉,就启用备用的memcache.支持分布式,空间不够可以扩容

配置案例的介绍链接,如下:https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration

tomcat自身不直接把会话保存在memcache中,需要借助开发工具来实现。相应的项目官方代码可以直接从github上搜到。资源链接:https://github.com/magro/memcached-session-manager

对tomcat而言,要把会话保存在memcache中,有以下几种类库

第一:要使用专门的会话管理工具memcache-session-manager的类库,如 memcached-session-manager-${version}.jar 和 memcached-session-manager-tc7-${version}.jar(如tomcat是7版本)这个类库

第二: 会话是保存在内存中,不支持流式化,借助流式化工具实现,流式化工具有如下四种,可以任意用一种,这些是类库,需要装入tomcat才能使用

kryo-serializer: msm-kryo-serializer, kryo-serializers-0.34+, kryo-3.x, minlog, reflectasm, asm-5.x, objenesis-2.x

javolution-serializer: msm-javolution-serializer, javolution-5.4.3.1

xstream-serializer: msm-xstream-serializer, xstream, xmlpull, xpp3_min

flexjson-serializer: msm-flexjson-serializer, flexjson

 第三:支持适配对应存储系统的类库,如要使得tomcat和memcached能通信,要有能适配memcached客户端的类库,如 spymemcached-${version}.jar。如果存储是redis,则要使用jedis-2.9.0.jar.

这里介绍流式工具为javolution-serializer的部署

例子

步骤一

前端调度器上不需要做绑定的设置,只需要配置调度即可

步骤二

找两台服务器172.18.50.62和63,安装memcached,并启用memcached服务。

yum -y  install memcached
systemctl restart memcached

步骤三

后端tomcat 72和73 在如下的路径放置相关的jar包

/usr/share/tomcat/webapps/test/WEB-INF/lib/路径下放置两个类库

javolution-5.4.3.1.jar 

msm-javolution-serializer-2.1.1.jar

/usr/share/tomcat/lib/路径下放置三个类库

memcached-session-manager-2.1.1.jar 

memcached-session-manager-tc7-2.1.1.jar 

spymemcached-2.12.3.jar

编辑配置文件server.xml,放置host配置段里,如下

vim  /usr/share/tomcat/conf/server
<Context path="/test" docBase="/usr/share/tomcat/webapps/test" reloadable="true">
              <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
                memcachedNodes="n1:172.18.50.62:11211,n2:172.18.50.63:11211"
                failoverNodes="n1"
                requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
                transcoderFactoryClass="de.javakaffee.web.msm.serializer.javolution.JavolutionTranscoderFactory"
              />
             </Context>

重启tomcat服务器

测试

在浏览器里输入http://172.18.50.75/test,不管怎么调度, session id始终不变,但是网页上的其他内容已经发生变化,说明实验成功。