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/ ProxyPassReverse / 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/ ProxyPassReverse / 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始终不变,但是网页上的其他内容已经发生变化,说明实验成功。