4. 结合反向代理实现Tomcat部署
4.1 常见部署方式介绍
图片.pngstandalone模式,Tomcat单独运行,直接接受用户的请求,不推荐。
反向代理,单机运行,提供了一个Nginx作为反向代理,可以做到静态由nginx提供响应,动态jsp代理给Tomcat
LNMT:Linux + Nginx + MySQL + Tomcat
LAMT:Linux + Apache(Httpd)+ MySQL + Tomcat
前置一台Nginx,给多台Tomcat实例做反向代理和负载均衡调度,Tomcat上部署的纯动态页面更适合
LNMT:Linux + Nginx + MySQL + Tomcat
多级代理
LNNMT:Linux + Nginx + Nginx + MySQL + Tomcat
4.2 利用nginx反向代理实现全部转发置指定的同一个虚拟主机
图片.png实验环境:
一台客户端, CentOS 7, 10.0.0.187
一台服务端, 安装nginx, 安装tomcat, 并且配置两个虚拟主机, 10.0.0.201
- 修改服务端hosts解析
vim /etc/hosts
10.0.0.201 node1.tomcat.org node2.tomcat.org #在服务端做域名解析, 因为, 之后在配置nginx转发时用到的是域名, 如果没有DNS解析,那么nginx无法得到后端服务器的ip地址
#127.0.0.1 node1.tomcat.org node2.tomcat.org #设置成127也可以, 因为nginx和tomcat在同一台主机
- 服务端安装nginx
yum -y install nginx
- 修改nginx配置文件, 实现反向代理功能
[21:10:59 root@201 ~]#vim /etc/nginx/nginx.conf
location / {
proxy_pass http://node1.tomcat.org:8080; #这里如果设置的是域名, 一定要确保本机有DNS解析, 而且这里的域名, 决定了用户访问nginx时, 会转发到哪个后端服务器. 如果这里写10.0.0.201:8080, 那么就会转发到tomcat默认虚拟主机
}
- 启动nginx
[21:14:50 root@201 ~]#systemctl enable --now nginx
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
- tomcat配置两个虚拟主机
<Host name="node1.tomcat.org" appBase="/data/node1" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="node1_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
<Host name="node2.tomcat.org" appBase="/data/node2" unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="node2_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
- 为虚拟主机创建站点目录
[21:23:48 root@201 ~]#mkdir /data/node{1..2}/ROOT -p
[21:24:20 root@201 ~]#echo node1.tomcat.org > /data/node1/ROOT/index.html
[21:24:46 root@201 ~]#echo node2.tomcat.org > /data/node2/ROOT/index.html
[21:31:38 root@201 ~]#systemctl restart tomcat
- 测试两个虚拟主机访问, 在201本地测试
[21:58:22 root@201 /usr/local/tomcat/logs]#curl node1.tomcat.org:8080
node1.tomcat.org
[21:58:30 root@201 /usr/local/tomcat/logs]#curl node2.tomcat.org:8080
node2.tomcat.org
- 修改客户端DNS解析, 添加nginx解析
10.0.0.201 nginx.tomcat.org
- 按照nginx反向代理定义的转发主机node1, 因为配置在了nginx的默认虚拟主机上, 此时客户访问nginx的域名或者ip时, 最后请求都是转发到node1上. 浏览器访问nginx.tomcat.org, 解析到10.0.0.201, 按照访问的域名, 匹配多个sever_name, 因为只有一个默认的localhost, 因此, 没有匹配, 所以转发给默认虚拟主机, 再通过location转发到node1
[13:07:16 root@client ~]#curl 10.0.0.201
node1.tomcat.org
[13:07:18 root@client ~]#curl nginx.tomcat.org
node1.tomcat.org
- 将后端服务器改为node2, 请求又会被转到node2上
[13:07:20 root@client ~]#curl nginx.tomcat.org
node2.tomcat.org
[16:02:09 root@client ~]#curl 10.0.0.201 # 访问ip也是看proxy_pass写的是哪个后端服务器
node2.tomcat.org
- 将后端服务器改为ip, 10.0.0.201, 那么用户的请求会被转到tomcat的默认页面
proxy_pass http://10.0.0.201:8080;
到这里, 实现了利用nginx反向代理将用户请求始终转发到后端的同一个虚拟主机, 这就造成了nginx不会根据用户访问的主机头信息做转发, 并且,此时无论用户访问什么资源, 都会被转到后端的指定的tomcat服务器上, 可是tomcat并不擅长处理静态资源, 因此还要实现动静分离, 将静态资源交给nginx处理, 动态资源请求转发给tomcat
如果希望nginx转发请求时, 传递请求报文首部的Host字段, 那么需要添加proxy_set_header Host $http_host; 并且, 在客户端把每个虚拟主机的域名都解析到nginx服务器的ip上, 并且为每个tomcat虚拟主机, 都配置一个nginx的虚拟主机, server_name相对应, location转发相对应, 这样请求转发到后端哪个服务器就由用户访问时决定, 否则默认与用户请求无关, 需要取决于proxy_pass http://FQDN;
- 恢复反向代理到node1
proxy_pass http://node1.tomcat.org:8080;
4.3 利用nginx实现动静分离代理
- 给node1和node2分别制作一个动态资源, index.jsp文件,放在ROOT下
<HTML>
<HEAD>
<TITLE>HelloWorld!</TITLE>
</HEAD>
<BODY>
<%
out.println("<h1>This is node1!</h1>");
%>
</BODY>
</HTML>
<HTML>
<HEAD>
<TITLE>HelloWorld!</TITLE>
</HEAD>
<BODY>
<%
out.println("<h1>This is node2!</h1>");
%>
</BODY>
</HTML>
- 测试用户访问index.jsp还是转发到node1
[16:03:35 root@client ~]#curl nginx.tomcat.org/index.jsp
<HTML>
<HEAD>
<TITLE>HelloWorld!</TITLE>
</HEAD>
<BODY>
<h1>This is node1!</h1>
</BODY>
</HTML>
- 利用nginx实现动静分离
指定location ~* \.jsp$ 转发到后端tomcat-node1
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / { #除了.jsp结尾的资源, 其余都用nginx本地 root /usr/share/nginx/html来处理
}
location ~* \.jsp$ { #如果访问的是.jsp结尾的动态资源, 就往node1转发
proxy_pass http://node1.tomcat.org:8080;
}
nginx -s reload
- 制作一个nginx静态测试页面
echo nginx-test.html > /usr/share/nginx/html/test.html
- 重启nginx
systemctl restart nginx
- 测试访问
[16:19:44 root@client ~]#curl nginx.tomcat.org/test.html
nginx-test.html
[16:19:46 root@client ~]#curl nginx.tomcat.org/index.jsp
<HTML>
<HEAD>
<TITLE>HelloWorld!</TITLE>
</HEAD>
<BODY>
<h1>This is node1!</h1>
</BODY>
</HTML>
- 将nginx转发改到node2, 再次测试动静分离
[16:19:57 root@client ~]#curl nginx.tomcat.org/index.jsp
<HTML>
<HEAD>
<TITLE>HelloWorld!</TITLE>
</HEAD>
<BODY>
<h1>This is node2!</h1>
</BODY>
</HTML>
[16:20:29 root@client ~]#curl nginx.tomcat.org/test.html
nginx-test.html
到此, 利用nginx location实现了动静分离, 静态资源交给nginx处理, 动态资源会交给nginx定义的转发后端服务器处理.
4.4 利用httpd实现基于http协议的反向代理至后端tomcat服务器
nginx做反向代理时不会保留用户请求的主机头信息, 具体转发到后端哪个tomcat虚拟主机上完全由nginx的反向代理控制,
比如, 客户端在DNS做解析, 把要访问的域名A解析成nginx的ip地址, 这样访问域名A后, 域名A是不会被转给后端的tomcat服务器的, 具体调度到哪台服务器上, 要看nginx中定义的proxy_pass. 也就是说, 用户访问的域名, 和后端tomcat虚拟主机的域名无关.
而httpd可以开启保留用户访问的主机头信息, 根据基于用户访问的主机头信息, 实现向指定的tomcat虚拟主机转发, 如果没有开启保留主机头信息或者访问的主机头不存在, 那么就由tomcat默认虚拟主机处理, tomcat上指定了哪个是默认虚拟主机, 就往哪个虚拟主机转发
httpd实现反向代理需要利用一个基于FQDN的虚拟主机. 在httpd上添加了基于FQDN的虚拟主机后, 默认站点就失效了, 并且该虚拟主机因为排在第一个并且只有一个虚拟主机, 因此, 它就变成了默认的, 因此, 无论用户访问哪个域名, 不管匹配还是不匹配ServerName, 最后都是交给该虚拟主机来处理, 而虚拟主机里定义了转发给后端的tomcat服务器. tomcat服务器接收到用户请求, 查看请求报文的主机头部信息, 对比server.xml中的host虚拟主机配置, 如果匹配, 就转给对应的虚拟主机处理, 如果不存在, 就交给默认虚拟主机处理. 这就是httpd实现转发的过程.
用户访问域名A, 虚拟主机接收到请求后保留域名A主机头, 将请求转发到后端对应的为A的tomcat虚拟主机上.
4.4.1 配置说明
图片.pngproxy_http_module模块代理配置:
vim /etc/httpd/conf.d/http-tomcat.conf
<VirtualHost *:80>
ServerName node1.tomcat.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>
ProxyRequests:Off 关闭正向代理功能, 开启反向代理
ProxyPass:反向代理指令
ProxyPassReverse:保留代理的response头不重写(个别除外)
ProxyPreserveHost:On时, 让反向代理保留原请求的Host首部转发给后端服务器,off 时则删除host首部转发
ProxyVia:On开启 代理的请求响应时提供一个response的via首部,默认值off, 该via值, 就是apache上虚拟主机的ServerName
4.4.2 配置案例
- 在10.0.0.201上停止nginx服务, 安装apache
[17:00:35 root@201 ~]#systemctl stop nginx
[17:00:40 root@201 ~]#yum -y install httpd
- 对后端Tomcat默认的虚拟主机, 和两个node生成不同的测试页面
[17:04:06 root@201 ~]#echo /usr/local/tomcat/webapps/ROOT/test.html > /usr/local/tomcat/webapps/ROOT/test.html # Tomcat默认虚拟主机
[17:10:41 root@201 ~]#echo /data/node1/ROOT/test.html > /data/node1/ROOT/test.html
[17:10:46 root@201 ~]#echo /data/node2/ROOT/test.html > /data/node2/ROOT/test.html
- 配置httpd虚拟主机
[17:16:13 root@201 ~]#vim /etc/httpd/conf.d/tomcat.conf
<VirtualHost *:80>
ServerName node1.magedu.org # 这里的ServerName没有实际作用
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On # 保留客户端发送的请求报文的主机头FQDN信息
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>
[17:16:30 root@201 ~]#systemctl restart httpd
- 客户端配置域名解析
10.0.0.201 nginx.tomcat.org
10.0.0.201 node1.tomcat.org node2.tomcat.org
- 测试访问
[17:23:14 root@client ~]#curl node1.tomcat.org/test.html # node1虚拟主机是真实存在的, 因此Tomcat交给对应的虚拟主机处理
/data/node1/ROOT/test.html
[17:23:29 root@client ~]#curl node2.tomcat.org/test.html # node2也一样
/data/node2/ROOT/test.html
[17:23:31 root@client ~]#curl nginx.tomcat.org/test.html # 而nginx.tomcat.org并不是Tomcat上存在的虚拟主机, 因此Tomcat收到请求后, 会交给默认虚拟主机处理
/usr/local/tomcat/webapps/ROOT/test.html
[17:23:36 root@client ~]#curl nginx.tomcat.org/test.html -I
HTTP/1.1 200
Date: Tue, 23 Mar 2021 09:27:04 GMT
Server: Apache/2.4.37 (centos)
Accept-Ranges: bytes
ETag: W/"41-1616490641000"
Last-Modified: Tue, 23 Mar 2021 09:10:41 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 41
Via: 1.1 node1.magedu.org # Via就是apache虚拟主机配置的ServerName
- 关闭 ProxyPreserveHost
ProxyPreserveHost Off
此时, 客户端的请求报文头部信息Host是不会转发给后端Tomcat服务器的, 因此, 无论客户端访问哪个域名, 最终都是由httpd反向代理定义的虚拟主机来处理, 而此前定义的是默认虚拟主机, 因此都会由默认虚拟主机来处理
[17:27:03 root@client ~]#curl node1.tomcat.org/test.html
/usr/local/tomcat/webapps/ROOT/test.html
[17:32:37 root@client ~]#curl node2.tomcat.org/test.html
/usr/local/tomcat/webapps/ROOT/test.html
[17:32:41 root@client ~]#curl nginx.tomcat.org/test.html
/usr/local/tomcat/webapps/ROOT/test.html
- 修改httpd中的转发策略
ProxyPass / http://node1.tomcat.org:8080/
ProxyPassReverse / http://node1.tomcat.org:8080/
[17:33:51 root@201 ~]#systemctl restart httpd
# 这时node1就变成了了默认的虚拟主机, 因此, 请求都会交给node1来处理
[17:32:44 root@client ~]#curl nginx.tomcat.org/test.html
/data/node1/ROOT/test.html
[17:34:15 root@client ~]#curl node2.tomcat.org/test.html
/data/node1/ROOT/test.html
[17:34:17 root@client ~]#curl node1.tomcat.org/test.html
/data/node1/ROOT/test.html
4.5 httpd和nginx反向代理tomcat对比
- 当代理tomcat多虚拟主机时
nginx做反向代理, 默认只能转发到proxy_pass规定的tomcat服务器上, 因为nginx接收到请求后, 不会保留请求报文的主机头名称, 而是直接把请求转发给proxy_pass设定好的后端服务器上.
apache做反向代理时, 在接受到用户请求后, 会保留请求的主机头信息, 并根据主机头名称, 转发给后端对应的tomcat服务器上的虚拟主机
所以, 在反向代理单机多个tomcat实例时, 使用apache作为反向代理, ProxyPass可以只写tomcat服务器的ip:port或者域名解析, ServerName没有作用, DNS解析, 按照不同实例的网站名称, 全部解析到apache服务器的ip地址, 客户端访问时, 需要访问哪个网站就写哪个网站的URL即可, 由于httpd会保留请求报文的主机头, 因此, apache在转发时会把主机头转给tomcat, 再由tomcat去决定转发给哪个tomcat虚拟主机
而使用nginx作为反向代理, 代理单机多实例tomcat时, 需要配置多个nginx虚拟主机, 利用server_name去接受客户端的请求, 不同实例的网站也是要解析到nginx服务器的地址, 利用不同的nginx虚拟主机先接受请求, 然后根据定义的location去转发到不同的tomcat实例
如果只是单tomcat的动静分离, 那么网站的域名需要解析到nginx上, 客户端访问域名会被解析到nginx, 之后根据访问的资源类型做转发, 如果是静态, 则交给nginx, 如果是.jsp, 则交给后端的tomcat, 这时利用nginx默认虚拟主机做转发即可
server {
server_name node1.tomcat.org;
location ~* \.jsp$ {
proxy_pass http://node1.tomcat.org:8080;
}
}
server {
server_name node2.tomcat.org;
location ~* \.jsp$ {
proxy_pass http://node2.tomcat.org:8080;
}
}
4.6 实现tomcat负载均衡
动态服务器的问题,往往就是并发能力太弱,往往需要多台动态服务器一起提供服务. 如何把并发的压力分摊,这就需要调度,采用一定的调度策略,将请求分发给不同的服务器,这就是Load Balance负载均衡
当单机的Tomcat,演化出多机多级部署的时候,一个问题便凸显出来,这就是Session 而这个问题的由来,都是由于HTTP协议在设计之初没有想到未来的发展
4.6.1 HTTP的无状态,有连接和短连接
无状态:指的是服务器端无法知道两次请求之间的联系,即使是前后2次请求来自同一个浏览器,也没有任何数据能够判断出是同一个浏览器的请求。后来可以通过cookie、session机制来判断。
浏览器端第一次HTTP请求服务器端时,在服务器端使用session这种技术,就可以在服务器端产生一个随机值即SessionID发给浏览器端,浏览器端收到后会保持这个SessionID在Cookie当中,这个Cookie值一般不能持久存储,浏览器关闭就消失。浏览器在每一次提交HTTP请求的时候会把这个SessionID传给服务器端,服务器端就可以通过比对知道是谁了
Session通常会保存在服务器端内存中,如果没有持久化,则易丢失
Session会定时过期。过期后浏览器如果再访问,服务端发现没有此ID,将给浏览器端重新发新的SessionID
更换浏览器也将重新获得新的SessionID
有连接:是因为它基于TCP协议,是面向连接的,需要3次握手、4次断开。
短连接:Http 1.1之前,都是一个请求一个连接,而Tcp的连接创建销毁成本高,对服务器有很大的影响。所以,自Http 1.1开始,支持keep-alive,默认以开启,一个连接打开后,会保持一段时间(可设置),浏览器再访问该服务器就使用这个Tcp连接,减轻了服务器压力,提高了效率。
服务器端如果故障,即使Session被持久化了,但是服务没有恢复前都不能使用这些SessionID
如果使用HAProxy或者Nginx等做负载均衡器,调度到了不同的Tomcat上,那么也会出现找不到SessionID的情况
4.6.2 会话保持方式
4.6.2.1 session sticky会话黏性
Session绑定
nginx:source ip, cookie
HAProxy:source ip, cookie
优点:简单易配置
缺点:如果目标服务器故障后,如果没有做sessoin持久化,就会丢失session
4.6.2.2 session复制集群
Tomcat自己提供的多播集群,通过多播将任何一台的session同步到其它节点
缺点:
Tomcat的同步节点不宜过多,互相即时通信同步session需要太多带宽
每一台都拥有全部session,内存损耗太多
4.6.2.3 session server(session共享)
session 共享服务器,使用memcached、redis做共享的Session服务器,此为推荐方式
4.6.3 实现Tomcat负载均衡规划
实验环境:
图片.png四台主机
CentOS 7作为客户端, 10.0.0.187
一台CentOS 8 作为nginx 10.0.0.201
两台CentOS 8作为tomcat服务器, t1.tomcat.org 10.0.0.82; t2.tomcat.org 10.0.0.83
4.6.3.1 客户端和nginx配置DNS解析
10.0.0.187, 客户端配置DNS解析, 访问域名的80端口时, 调度到10.0.0.201 nginx, 由nginx进行调度
10.0.0.201 www.tomcat.org # 网站名
10.0.0.201安装nginx, 在nginx上配置DNS解析
10.0.0.82 t1.tomcat.org t1
10.0.0.83 t2.tomcat.org t2
4.6.3.2 配置tomcat虚拟主机
在tomcat创建虚拟主机, 并把t1和t2分别设置为默认虚拟主机, 这样不管能否匹配到url或者是用ip地址访问, 都会访问到我们设定的虚拟主机, 否则会访问默认页面
t1:
<Engine name="Catalina" defaultHost="t1.tomcat.org">
<Host name="t1.tomcat.org" appBase="/data/webapps"
unpackWARs="true" autoDeploy="true">
</Host>
mkdir /data/webapps/ROOT -pv
jsp测试页面
vim /data/webapps/ROOT/index.jsp
<%@ page import="java.util.*"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>tomcat test</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>
chown -R tomcat.tomcat /data/webapps
systemctl restart tomcat
t2
<Engine name="Catalina" defaultHost="t2.tomcat.org">
<Host name="t2.tomcat.org" appBase="/data/webapps"
unpackWARs="true" autoDeploy="true">
</Host>
mkdir /data/webapps/ROOT -pv
jsp测试页面
vim /data/webapps/ROOT/index.jsp
<%@ page import="java.util.*"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>tomcat test</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>
chown -R tomcat.tomcat /data/webapps
systemctl restart tomcat
4.6.4 实现利用nginx对后端tomcat进行负载均衡调度
4.6.4.1 配置nginx
nginx配置如下
http { #http语句块定义负载均衡后端服务器
upstream tomcat-server {
#ip_hash #先禁用看看轮询,之后开启粘性
#hash $cookie_JSESSIONID #先禁用看看轮询,之后开启粘性
server t1.tomcat.org:8080;
server t2.tomcat.org:8080;
}
server {
location ~* \.jsp$ {
proxy_pass http://tomcat-server; #server语句块定义调度的upstream服务器组和动静分离
}
}
开启nginx服务
systemctl start nginx
准备nginx静态默认页面
[19:43:02 root@nginx ~]#echo nginx-10.0.0.201 > /usr/share/nginx/html/index.html
测试分别访问静态和动态资源
访问静态资源时, 由nginx本地处理
图片.png访问动态资源时, 调度到后端tomcat处理, 并且默认是轮训机制, 每次访问从服务端获取的session id都不一样
用户第一次访问被调度到了201, 201会给用户发一个自己的sessionId, 下次用户访问时会携带这个sessionid, 但是调度是基于轮询的, 因此下一次用户被调度到了202, 并且访问时携带者201给自己的sessionid, 但是202上是没有201的session信息的, 因此就会生成一份自己的sessionid给用户. 用户再次访问, 携带202给的sessionid, 结果又被调度会来201, 然后201还是没有这个202的sessionid, 循环往复, 每次访问都会生成新的sessionid, 无法持久保存
图片.png 图片.png4.6.4.2 实现session绑定
- 基于源地址哈希
upstream tomcat-server {
ip_hash;
server t1.tomcat.org:8080;
server t2.tomcat.org:8080;
}
此时来自同一个ip的访问, 会始终调度到同一个服务器, sessionid也不变
图片.png 图片.png- 基于cookie调度
upstream tomcat-server {
hash $cookie_JESSIONID;
server t1.tomcat.org:8080;
server t2.tomcat.org:8080;
}
此时, 只要访问携带同一个sessionid, 就调度到同一台机器
图片.png 图片.png
网友评论