Tomcat基础《一》----WEB技术
Tomcat基础《二》----Tomcat基础介绍
负载均衡
动态服务器的问题,往往就是并发能力太弱,往往需要多台动态服务器一起提供服务。如何把并发的压
力分摊,这就需要调度,采用一定的调度策略,将请求分发给不同的服务器,这就是Load Balance负载
均衡。
当单机的Tomcat,演化出多机多级部署的时候,一个问题便凸显出来,这就是Session。而这个问题的
由来,都是由于HTTP协议在设计之初没有想到未来的发展。
HTTP的无状态,有连接和短连接
无状态:指的是服务器端无法知道2次请求之间的联系,即使是前后2次请求来自同一个浏览器,也没有任何数据能够判断出是同一个浏览器的请求。后来可以通过cookie、session机制来判断。
浏览器端第一次HTTP请求服务器端时,在服务器端使用session这种技术,就可以在服务器端产生一个随机值即SessionID发给浏览器端,浏览器端收到后会保持这个SessionID在Cookie当中,这个Cookie值一般不能持久存储,浏览器关闭就消失。浏览器在每一次提交HTTP请求的时候会把这个SessionID传给服务器端,服务器端就可以通过比对知道是谁了
Session通常会保存在服务器端内存中,如果没有持久化,则易丢失,为了速度,用了字典的技术
Session会定时过期。过期后浏览器如果再访问,服务端发现没有此ID,将给浏览器端重新发新的SessionID
更换浏览器也将重新获得新的SessionID
有连接:是因为HTTP1.x基于TCP协议,是面向连接的,需要3次握手、4次断开。
短连接: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服务器。
规划
主机名 t0 服务为调度器 nginx httpd IP:192.168.32.7
主机名 t1 服务为Tomcat1 JDK8、Tomcat8 IP:192.168.32.17
主机名 t2 服务为Tomcat2 JDK8、Tomcat8 IP:192.168.32.27
每台主机的域名解析,这里每一台主机都要进行DNS域名解析
192.168.32.7 t0.likai.com t0
192.168.32.17 t1.likai.com t1
192.168.32.27 t2.likai.com t2
在window10 中也可以添加域名解析,方面直接访问更加直观:
C:\Windows\System32\drivers\etc\hosts
192.168.32.7 t0.likai.com t0
192.168.32.17 t1.likai.com t1
192.168.32.27 t2.likai.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 #在tomcat的2台服务器中创建该目录
编写测试jsp文件,内容在下面
# vim /data/webapps/ROOT/index.jsp
[root@node1 ROOT]#cat index.jsp #在tomcat的2台服务器中都是一样的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>
# scp -r server.xml 192.168.32.27:/usr/local/tomcat
启动Tomcat服务
# startup.sh
测试用jsp
t1和t2节点的/data/webapps/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> #sessionid 变化
<%=new Date()%> #当前时间
</body>
</html>
t1虚拟主机配置
[root@node1 tomcat]#pwd
/usr/local/tomcat
[root@node1 tomcat]#vim conf/server.xml
<Engine name="Catalina" defaultHost="t1.likai.com"> #后面可以加jvmRoute="Tomcat1" 进行标识
</Host>
<Host name="t1.likai.com" appBase="/data/webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
t2虚拟主机配置
[root@node2 tomcat]#pwd
/usr/local/tomcat
[root@node2 tomcat]#vim conf/server.xml
<Engine name="Catalina" defaultHost="t2.likai.com">
</Host>
<Host name="t2.likai.com" appBase="/data/webapps" autoDeploy="true" />
</Host>
</Engine>
访问查看是否启动tomcat:
t1 域名访问
t1 ip地址访问
t2 域名访问
t2 ip地址访问
Nginx调度
# yum install nginx
# cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
# vim /etc/nginx/nginx.conf
# nginx -t
# systemctl start nginx.service
nginx配置如下
upstream tomcats { #配置在server之外 和server同级
#ip_hash; # 先禁用看看轮询,之后开启开黏性
server t1.magedu.com:8080;
server t2.magedu.com:8080;
}
server {
location ~* \.(jsp|do)$ { #或者直接是根/ 全部代理 这里只是代理.jsp和.do结尾的文件
proxy_pass http://tomcats;
}
}
[root@node0 ~]#nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@node0 ~]#ss -ntlp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:80 *:*
users:(("nginx",pid=11784,fd=6),("nginx",pid=11783,fd=6),("nginx",pid=11782,fd=6))
LISTEN 0 128 *:22 *:*
users:(("sshd",pid=6369,fd=3))
LISTEN 0 100 127.0.0.1:25 *:*
users:(("master",pid=6591,fd=13))
LISTEN 0 128 :::80 :::*
users:(("nginx",pid=11784,fd=7),("nginx",pid=11783,fd=7),("nginx",pid=11782,fd=7))
LISTEN 0 128 :::22 :::*
users:(("sshd",pid=6369,fd=4))
LISTEN 0 100 ::1:25 :::*
users:(("master",pid=6591,fd=14))
测试http://t0.likai.com/index.jsp,可以看到轮询调度效果。
[root@node0 ~]#while :;do curl t0.likai.com ;sleep 1;done
<div>On tomcats</div>
<div>192.168.32.17:8080</div>
<div>SessionID = <span style="color:blue">285FDA55692674DF3925A37D2553FA5F.Tomcat1</span></div>
Sat Jan 18 20:37:45 CST 2020
<div>On tomcats</div>
<div>192.168.32.27:8080</div>
<div>SessionID = <span style="color:blue">AB9F8E1D1AE0879A1E8917674376453C.Tomcat2</span></div>
Sat Jan 18 20:37:46 CST 2020
<div>On tomcats</div>
<div>192.168.32.17:8080</div>
<div>SessionID = <span style="color:blue">C8C4F14B65C6F4177D431D0C48D97DFA.Tomcat1</span></div>
Sat Jan 18 20:37:47 CST 2020
<div>On tomcats</div>
<div>192.168.32.27:8080</div>
<div>SessionID = <span style="color:blue">A96F3BE11E76F2883B9C0A2972BB3E87.Tomcat1</span></div>
Sat Jan 18 20:37:48 CST 2020
#调度的时候SessionID会发生变化
访问t1
访问t12
在upstream中使用ip_hash指令,使用客户端IP地址Hash。这个hash值使用IPv4地址的前24位或全部的IP v6地址。
源地址会保持不变,但是sessionID依旧会改变,但是ip地址会被粘住,不改变了。
Httpd调度
使用httpd -M 可以看到proxy_balancer_module,用它来实现负载均衡。
#yum install httpd -y
关闭httpd默认主机
# cd /etc/httpd/conf
# vim httpd.conf
注释掉 #DocumentRoot "/var/www/html" #否则会看到默认的首页 :119行
#pwd
/etc/httpd/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参数
ProxySet指令也可以使用上面的参数。
在tomcat的配置中Engine使用jvmRoute属性
[root@node2 tomcat]#pwd
/usr/local/tomcat
[root@node2 tomcat]#vim conf/server.xml
t1、t2的tomcat配置中分别增加jvmRoute
<Engine name="Catalina" defaultHost="t1.likai.com" jvmRoute="Tomcat1">
<Engine name="Catalina" defaultHost="t2.likai.com" jvmRoute="Tomcat2">
</Host>
<Host name="t1.likai.com" appBase="/data/webapps" unpackWARs="true" autoDeploy="true">
</Host>
</Host>
<Host name="t2.likai.com" appBase="/data/webapps" autoDeploy="true" />
</Engine>
这样SessionID,就变成了这样SessionID = 9C949FA4AFCBE9337F5F0669548BD4DF.Tomcat2
conf.d/vhosts.conf内容如下
<VirtualHost *:80> #全部调度80端口
ProxyRequests Off #正向代理关闭
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
</VirtualHost>
<Proxy balancer://lbtomcats>
BalancerMember http://t1.likai.com:8080 loadfactor=1 #loadfactor调度因子比例
BalancerMember http://t2.likai.com:8080 loadfactor=2
</Proxy>
loadfactor设置为1:2,便于观察。观察调度的结果是轮询的。
[root@node0 ~]#httpd -t
Syntax OK
[root@node0 ~]#ss -ntlp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:80 *:*
users:(("httpd",pid=12130,fd=3),("httpd",pid=12129,fd=3),("httpd",pid=12128,fd=3),
("httpd",pid=12127,fd=3),("httpd",pid=12126,fd=3),("httpd",pid=12125,fd=3))
LISTEN 0 128 *:22 *:*
users:(("sshd",pid=6369,fd=3))
LISTEN 0 100 127.0.0.1:25 *:*
users:(("master",pid=6591,fd=13))
LISTEN 0 128 :::22 :::*
users:(("sshd",pid=6369,fd=4))
LISTEN 0 100 ::1:25 :::*
users:(("master",pid=6591,fd=14))
访问都是轮询的方式 按照1:2的调度
[root@node0 ~]#while :;do curl t0.likai.com ;sleep 1;done #t0.likai.com域名对应的ip为192.168.32.7
<div>On t0.likai.com</div>
<div>192.168.32.27:8080</div>
<div>SessionID = <span style="color:blue">BB57E96162B48B433318C70883C491AD.Tomcat2</span></div>
Sat Jan 18 21:12:23 CST 2020
<div>On t0.likai.com</div>
<div>192.168.32.27:8080</div>
<div>SessionID = <span style="color:blue">9E84960E58A1B31B4BE0135EC117FB0C.Tomcat2</span></div>
Sat Jan 18 21:12:24 CST 2020
<div>On t0.likai.com</div>
<div>192.168.32.17:8080</div>
<div>SessionID = <span style="color:blue">13AFB8EC49428221AA0162AC8CC91B1C.Tomcat1</span></div>
Sat Jan 18 21:12:25 CST 2020
<div>On t0.likai.com</div>
<div>192.168.32.27:8080</div>
<div>SessionID = <span style="color:blue">728490CED7B0DC6B1E0074099D5D3F48.Tomcat2</span></div>
Sat Jan 18 21:12:26 CST 2020
<div>On t0.likai.com</div>
<div>192.168.32.27:8080</div>
<div>SessionID = <span style="color:blue">EA6CE5844F38F45201D318BA0804A237.Tomcat2</span></div>
Sat Jan 18 21:12:27 CST 2020
<div>On t0.likai.com</div>
<div>192.168.32.17:8080</div>
<div>SessionID = <span style="color:blue">B65D993DCE4614ED38FF71557CAD8E6D.Tomcat1</span></div>
Sat Jan 18 21:12:28 CST 2020
使用session黏性
修改conf.d/vhosts.conf
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED #关键代码
<VirtualHost *:80>
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
</VirtualHost>
<Proxy balancer://lbtomcats>
BalancerMember http://t1.likai.com:8080 loadfactor=1 route=Tomcat1#route=Tomcat1和jvmRoute="Tomcat1保持一致
BalancerMember http://t2.likai.com:8080 loadfactor=2 route=Tomcat2 #这个route也要记得加上
ProxySet stickysession=ROUTEID #关键代码 以session为力度
</Proxy>
发现Session不变了,一直找的同一个Tomcat服务器。
[root@node0 ~]#while :;do curl t0.likai.com ;sleep 1;done
<div>On t0.likai.com</div>
<div>192.168.32.17:8080</div>
<div>SessionID = <span style="color:blue">9F3E60DDD7BB3B2B140F20A5FF42A38A.Tomcat1</span></div>
Sat Jan 18 21:20:54 CST 2020
<div>On t0.likai.com</div>
<div>192.168.32.17:8080</div>
<div>SessionID = <span style="color:blue">3DD63855DB4F073FC827C793E43E7E5A.Tomcat1</span></div>
Sat Jan 18 21:20:55 CST 2020
<div>On t0.likai.com</div>
<div>192.168.32.17:8080</div>
<div>SessionID = <span style="color:blue">5DCF9C47C295A5E822CFC9CBE240A8F5.Tomcat1</span></div>
Sat Jan 18 21:20:56 CST 2020
ip地址粘住,都是调度到Tomcat1 同一个服务器
ajp调度
修改conf.d/vhosts.conf
<VirtualHost *:80>
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
</VirtualHost>
<Proxy balancer://lbtomcats>
BalancerMember ajp://t1.likai.com:8009 loadfactor=1 route=Tomcat1 #只需要改协议以及端口即可
BalancerMember ajp://t2.likai.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 中的For the impatient 这节参考
以下是一些重要的默认值:
组播地址是228.0.0.4
组播端口为45564(端口和地址共同决定群集成员资格。
广播的IP是java.net.InetAddress.getLocalHost().getHostAddress()(确保您不广播127.0.0.1,这是一个常见错误)
侦听复制消息的TCP端口是范围中的第一个可用服务器套接字 4000-4100
侦听器已配置 ClusterSessionListener
配置了两个拦截器TcpFailureDetector,MessageDispatchInterceptor
多台机器时间必须同步
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager" #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"一致算一个多播复制组
port="45564"
frequency="500" #频率
dropTime="3000"/> #丢弃时间
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" #异步I0
address="auto" #单播 auto自动绑定的是127.0.0.1 一般需要修改 需要指定一个本机有的地址
port="4000"
autoBind="100" #4000~4100端口地址范围
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 #拦截器 解决TCP失败如何处理 消息通讯拦截修改
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"/> #放到<Engube><HOST>中
</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
主机名 t0 服务为调度器 nginx httpd IP:192.168.32.7
主机名 t1 服务为Tomcat1 JDK8、Tomcat8 IP:192.168.32.17
主机名 t2 服务为Tomcat2 JDK8、Tomcat8 IP:192.168.32.27
本次把多播复制的配置放到缺省虚拟主机里面, 即Host之下。
特别注意修改Receiver的address属性为一个本机可对外的IP地址。
要在Tomcat 8容器中运行会话复制,应完成以下步骤:
您的所有会话属性都必须实现 java.io.Serializable
取消注释Clusterserver.xml中的元素
如果定义了自定义集群阀,请确保ReplicationValve 在server.xml的Cluster元素下也有定义
如果您的Tomcat实例在同一台机器上运行,请确保Receiver.port 每个实例的属性都是唯一的,在大多数情况下,Tomcat足够聪明,可以通过自动检测4000-4100范围内的可用端口自行解决此问题
确保你web.xml有 <distributable/>元素
如果您使用的是mod_jk,请确保在Engine上设置<Engine name="Catalina" jvmRoute="node01" > 了jvmRoute属性,并且jvmRoute属性值与worker.properties中的工作程序名称匹配。
确保所有节点都具有相同的时间并与NTP服务同步!
确保将您的负载均衡器配置为粘性会话模式。
[root@node1 tomcat]#cat conf/web.xml |tail -10
<!-- here, so be sure to include any of the default values that you wish -->
<!-- to use within your application. -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<distributable/> #表示开启集群 一定要添加
</web-app>
t1的server.xml中,如下
<Host name="t1.likai.com" appBase="/data/webapps" autoDeploy="true" >
其他略去
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="192.168.32.17" #修改对应的ip地址
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
t2的server.xml中,如下
<Host name="t2.likai.com" appBase="/data/webapps" autoDeploy="true" >
其他略去
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="192.168.32.27"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
Tomcat重启后,ss命令能看到tomcat监听在4000端口上
尝试使用刚才配置过得负载均衡(移除Session黏性),测试发现Session还是变来变去。
准备web.xml
在应用中增加WEB-INF,从全局复制一个web.xml过来
#mkdir /data/webapps/ROOT/WEB-INF/
# cp /usr/local/tomcat/conf/web.xml /data/webapps/ROOT/WEB-INF/
为web.xml的 标签增加子标签 来开启该应用程序的分布式。
重启全部Tomcat,通过负载均衡调度到不同节点,返回的SessionID不变了。
[root@node1 tomcat]#ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 100 :::8009 :::*
LISTEN 0 100 :::8080 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 50 ::ffff:192.168.32.17:4000 #绑定在127.0.0.1错误 :::*
LISTEN 0 1 ::ffff:127.0.0.1:8005 :::*
[root@node2 tomcat]#ss -ntl
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 100 :::8009 :::*
LISTEN 0 100 :::8080 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
LISTEN 0 50 ::ffff:192.168.32.27:4000 #绑定在127.0.0.1错误 :::*
LISTEN 0 1 ::ffff:127.0.0.1:8005 :::*
来源:CSDN
作者:Magedu-M39-李凯
链接:https://blog.csdn.net/weixin_45651006/article/details/104023156