今天我们来聊一聊用Nginx和httpd对Tomcat集群做负载均衡的配置以及需要注意的点;在前边的演示和配置都是以单台tomcat来配置使用;但是在生产中单台tomcat实在支撑不了大规模的访问,这个时候我们就需要考虑把多台tomcat做成集群对外提供服务;多台tomcat做成集群对外提供服务就必然要有一个调度器来对客户端的请求做调度,常用的调度器有nginx httpd haproxy lvs等等;用这些调度器来对tomcat做负载均衡的配置和对其他web服务器做负载均衡的配置没有本质的不同;我们都可以把tomcat当作web服务器来配置就好;
1、环境准备
运行Docker 启动两个tomcat容器当作后端tomcat server 并且把两台tomcat容器的网页目录分别用存储卷的方式映射到/tomcat/doc/tomcat1和tomcat1目录
提示:以上就运行了两个tomcat容器,分别是tct1和tc2,并且我们把/usr/local/tomcat/webApps/myapp映射到宿主机的/tomcat/doc/tomcat1和tomcat2,这样做我们就可以直接把网页脚本放到宿主机上的这个目录从而实现把网页部署到tomcat的默认虚拟主机上;
编辑两个容器的主页文件内容
提示:以上分别给tomcat1和tomcat2提供了一个测试主页;
现在分别放tomcat1和tomcat2看看对应主页是否能够访问到
提示:可以看到tomcat1和tomcat2都能够访问到,到此后端tomcat的环境就准备好了;接下来我们来配置nginx来对他们做负载均衡;
2、配置nginx对tomcat做负载均衡
提示:以上配置就是把两台tomcat容器归并为tcsevs组,然后反代/的访问到这个组上即可。这样配置默认是轮询的;
验证:访问宿主机上的80端口看看是否分别能够访问到后端两台tomcat容器提供的主页?
检查nginx配置文件语法格式并启动nginx
访问宿主机的80端口,看看是否能够访问到后端tomcat提供的页面?
提示:可以看到访问宿主机的80端口是能够正常访问到后端tomcat服务器上的,并且也看出了默认轮询调度的效果;但是这里存在一个问题,同一用户访问宿主机的80端口,给我们响应的结果session id都不同,这意味着nginx并没有追踪到用户的状态信息,原因是因为http请求本来就是无状态的,为了让服务记录用户的状态信息,在nginx上我们可以基于源ip做调度,什么意思呢,就是同一源ip地址,nginx都把该请求调度到同一台后端server上,使得同一用户访问的状态信息始终调度到同一后端server上;
nginx基于源ip做会话保持
提示:ip_hash和hash $remote_addr都表示对源ip进行哈希计算,然后把取得到结果和总权重做模运算,结果落到那个节点,就调度到那个节点;什么意思呢,如上所示,后端server有两个,且权重都为1,那么他们的权重和就是2,ip_hash和hash $remote_addr就是把客户端的ip地址的前三段进行hash计算,然后把得到的值再和权重和做取模运算,很显然取模后端结果要么是0要么是1,如果取模后的结果是1,那么nginx基于它内部的对应关系,把该请求就调度到tomcatB或者tomcatA;
测试:重启niginx ,访问宿主机的80端口看看是否都把请求调度到同一后端server上?
提示:可以看到现在访问宿主机的80端口就没有在轮询了,而是始终调度到tomcatA这台server上进行响应;但是我们访问127.0.0.1的80端口它又调度到tomcatB上去了,这是因为nginx的调度算法中hash $remote_addr 和ip_hash是把IP地址的前24位做hash,所以如果你的IP前三段相同时,nginx它会认为是和nginxserver是同一局域网,所以它会把请求调度到同一局域网之前来请求过的后端server上进行响应;当然除了我们可以对源地址做hash,我们也可以对其他首部做hash计算,原理都是类似的,都是把对应首部的值做hash计算,然后同权重和做取模运算;然后根据nginx内部的对应关系,把取模后端结果相同的请求调度到同一后端server,就是基于这样的原理,把客户端和后端server绑定到一起实现了会话绑定;
httpd对tomcat做负载均衡
httpd做负载均衡器,需要确认httpd是否开启了proxy_http_module、proxy_module 、proxy_balancer_module如果需要用到ajp还需要确定proxy_ajp_module模块是否启用,以及调度算法的三个模块 lbmethod_bybusyness_module 、lbmethod_byrequests_module、lbmethod_bytraffic_module;以上模块对于调度算法来说用到那个启用那个也行,对于http或者ajp也是一样的;用得到就启用,用不上不启用也没关系;
提示:可以看到我们需要用的模块都是启用了的;
配置httpd对后端tomcat 做负载均衡
提示:从上面的配置,其实感觉和nginx的配置逻辑很相似,首先把后端server归并成一个组,然后反代时把请求代理到定义的组上即可;这里说一下调度算法吧,proxyset lbmethod 用来指定调度算法的,默认不写是使用byrequests,这个算法就是httpd里的轮询调度算法,当然在每个balancermember 后面加上权重,就成了加权轮询了;除此调度算法,我们还可以使用bytraffic,这个调度算法是根据和后端server的传输流量来调度,如果某个服务器传输流量很大,那么他会把请求往传输流量相对小的服务器上调度;bybusyness这个调度算法是根据后端server的繁忙程度来调度;类似nginx里的least_conn最少连接算法;对balancermember 我们也可以向nginx 那样设置单独属性,只需要在后面写上对应的属性即可;常用的属性有status 这个属性表示表示对应balancermember是处于什么状态,其中对status有6种取值;D表示禁用对应server,不提供任何请求;S表示人工手动标识为不可用;I表示强制上线模式(强制忽略错误模式);H表示热备模式(相当于nginx里的backup,只有组里的其他server都不可用时,它才会被激活,用于say sorry);E表示强制处于错误模式(即便没有错误也要让他处于有错误);N表示排干模式;除了status来指定balancermember的状态,还可以使用loadfactor来指定权重,类似于nginx里的weight;
停掉nginx,检查httpd 的配置文件语法,如果没有问题就启动httpd
访问httpd提供的服务,看看是否访问到后端tomcat的页面
提示:可以看到和nginx的访问一样,都可以实现轮询;
httpd基于cookie对后端tomcat做会话粘性
提示:以上配置表示给客户端请求cookie首部添加一个标识,ROUTEID=%{BALANCER_WORKER_ROUTE}e表示,我们指定的ROUTEID标识的值为balancermember 后面的route属性指定的值;env=BALANCER_ROUTE_CHANGED表示,如果我们指定的route的值发生变化时,它需要重新调度;简单讲就是给cookie信息打标签;proxyset stickysession=ROUTEID 表示给该组所有成员设置会话粘性KEY的名称为ROUTEID,这个值通常要和上面的set-cookie后面的KEY对应;如果把它写到每个balancermember后面表示单独给某个server设置会话粘性KEY的名称;如果写在proxy配置段里需要用proxyset指令来设置,表示给该组的所有member设置 stickysession;简单讲就是声明以那个key来当做会话粘性的基准来做调度;这个逻辑和haproxy里面的会话保持设定类似;有关haproxy配置会话保持可以参考https://www.cnblogs.com/qiuhom-1874/p/12776261.html;
测试:检查httpd的配置文件语法,如果没有问题就重启httpd,然后访问httpd看看会有什么变化
用curl 来模拟第一次访问httpd服务器,看看响应首部有什么变化?
提示:可以看到访问httpd服务器,在响应首部会多一个set-cookie首部,并且该首部的的值就是我们之前在配置文件中配置的KEY和value;set-cookie首部主要是在浏览器下次请求时,它会把set-cookie首部的值用cookie首部携带去访问服务器,这样一来,服务器就可根据客户端请求报文的cookie的值,来分析本次请求是那个客户端发送过来,后续服务端该怎么调度;
用浏览器访问,看看客户端后续的请求,是不是把第一次访问中的set-cookie的值拿上去请求服务端?
提示:可以看到浏览器第一次访问,服务器会在响应首部中添加一个set-cookie的首部;这个首部的值就是ROUTEID是目前响应我们的后端server上的route的值;
提示:可以看到客户端在请求首部cookie中,把之前set-cookie中的值都携带过去了;此时httpd收到客户端请求就可以根据设置的stickysession 指定的KEY来判断该把对应请求发送到那个后端server上进行响应了;这样一来,只要客户端的cookie不变,那么它每次访问服务端都会以cookie首部的值去告诉服务端该调度到那台后端server上;
用curl模仿客户端请求携带cookie访问服务端
[root@docker_node01 ~]# curl -I --cookie "ROUTEID=.TOMCAT1" http://192.168.0.22/myapp/
HTTP/1.1 200
Date: Mon, 20 Jul 2020 14:26:02 GMT
Content-Type: text/html;charset=ISO-8859-1
Transfer-Encoding: chunked
Set-Cookie: JSESSIONID=F03BABD6CC4905066B3BF78947D024CC; Path=/myapp; HttpOnly
Via: 1.1 www.test1.com
[root@docker_node01 ~]# curl --cookie "ROUTEID=.TOMCAT1" http://192.168.0.22/myapp/
<html>
<head><title>TomcatA</title></head>
<body>
<h1><font color="blue">TomcatA</font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<td>FDD5095817FBEE6B58054666B64E46C2</td>
</tr>
<tr>
<td>Created on</td>
<td>1595255172651</td>
</tr>
</table>
</body>
</html>
[root@docker_node01 ~]# curl --cookie "ROUTEID=.TOMCAT2" http://192.168.0.22/myapp/
<html>
<head><title>TomcatB</title></head>
<body>
<h1><font color="green">TomcatB</font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<td>BC5EAC6B1B75E54F4C92CCB0B5018808</td>
</tr>
<tr>
<td>Created on</td>
<td>1595255213765</td>
</tr>
</table>
</body>
</html>
[root@docker_node01 ~]#
提示:可以看到当我们使用curl模仿客户端访问携带cookie时,在响应首部就不会在给我们发set-cookie首部(这里的set-cookie是指和我们在服务器设定相关的首部),并且我们携带不同ROUTEID的cookie,它会根据我们携带的ROUTEID的值把我们调度到不同的后端server上进行响应;对于httpd负载均衡代理后端tomcat用ajp的配置方式和http的配置方式一样的,不同的只是把后端server的http协议修改成ajp,后端tomcat的端口修改成ajp协议监听的端口即可,默认tomcatajp协议监听在8009端口;
配置httpd后端管理界面页
提示:以上配置表示启动httpd管理页面,并绑定到/manager-page这个uri上,对于/manager-page这个uri不做任何代理,并且该rui只能允许ip地址为192.168.0.232的主机访问,其他主机都没有权限,包括服务器本身;
验证:用非192.168.0.232的主机访问192.168.0.22/manager-page看看是否能够访问到?
[root@docker_node01 ~]# ip a l ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:0c:29:22:36:7f brd ff:ff:ff:ff:ff:ff
inet 192.168.0.22/24 brd 192.168.0.255 scope global ens33
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fe22:367f/64 scope link
valid_lft forever preferred_lft forever
[root@docker_node01 ~]# httpd -t
Syntax OK
[root@docker_node01 ~]# systemctl restart httpd
[root@docker_node01 ~]# curl http://192.168.0.22/manager-page
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /manager-page
on this server.</p>
</body></html>
[root@docker_node01 ~]#
提示:可以看到用192.168.0.22去访问,提示403没有权限;
用192.168.0.232去访问,看看是否能够访问到管理页面?
提示:用192.168.0.232上的浏览器上可以正常访问到httpd的管理页面的;
动态修改tomcat1的权重
提示:正因为这个页面可以动态的更改后端服务器的属性,所以通常需要做访问限制;