tomcat通过 nginx 实现负载均衡高可用

5 tomcat通过 nginx 实现负载均衡高可用

负载均衡:

  • 动态服务器的问题,往往就是并发能力太弱,往往需要多台动态服务器一起提供服务。如何把并发的压力分摊,就需要调度,采用一定的调度策略,将请求分发给不同的服务器,这就是 Load Balance 负载均衡。

  • 当单机的 Tomcat,演化出多机多级部署的时候,一个问题便凸显出来,这就是 Session。而这个问题的由来,都是由于 HTTP 协议的设计之初没有想到未来的发展。

5.1 HTTP 的无状态,有连接和短连接(HTTP的三大特点)

  • 无状态:指的是服务器端无法知道 2 次请求之间的联系,即使是前后 2 次请求来自同一个浏览器,也没有任何数据能够判断出是同一个浏览器的请求。后来可以通过 cookie、session 机制来判断。所谓的cookie就是连接复用。

    • 浏览器端第一次 HTTP 请求服务器端时,在服务器端使用 session 这种技术,就可以在服务器端产生一个随机值即 Session ID 发给浏览器端,浏览器端收到后会保持这个 Session ID 在 Cookie 当中,这个Cookie 值不能持久存储,浏览器关闭就消失。浏览器在每一次提交 HTTP 请求的时候会把这个 Session ID 传给服务器端,服务器端就可以通过比对知道是谁了

    • Session 通常会保存在服务器端内存中,如果没有持久化,则易丢失

    • Session 会定时过期。过期后浏览器如果再访问,服务端发现没有此 ID,将重新获得新的 Session ID

    • 更换浏览器也将重新获得新的 Session ID

  • 有连接:是因为它基于 TCP 协议,是面向连接的,需要 3 次握手、4 次断开。

  • 短连接:Http1.1 之前,都是一个请求一个连接,而 Tcp 的连接创建销毁成本高,对服务器有很大的影响。所以,自从 Http 1.1 开始,支持 keep-alive ,默认也开启,一个连接打开后,会保持一段时间(可设置),浏览器再访问该服务器就使用这个 Tcp 连接,减轻了服务器压力,提高了效率。

服务器端如果故障,即使 Session 被持久化了,但是服务没有恢复前都不能使用这些 Session ID。

如果使用 HAProxy 或者 Nginx 等做负载均衡器,调度到了不同的 Tomcat 上,那么也会出现找不到 Session ID 的情况。

5.1.1 Cookie 详解

Cookie,是在客户端的浏览器上做一个回话的保持,这个就是所谓的客户端保持技术。最终是存储在浏览器上的,cookie分两种:

1、会话级 cookie

2、持久存储带过期的(甚至还可以永久不过期)所以当浏览器拿到数据之后,将 cookie 值收到了。然后我浏览器会以此域名相关的域,可以是这个 cookie 值,所以当浏览器对同域发起请求的时候他会带上这个没有过期的cookie 值放到请求报文头部的中去,这样服务器端一看 cookie 来了,就会实现持久化

5.1.2 Session 详解

Session这个技术依赖于cookie这个技术做了一些改动。

Session 会在 cookie 里面搞一个特殊的 cookie 值,一个 K V(键值对)。这里面放的就是 session ID 与他等于的hash 值。然后服务器端会将这个值发回给客户端的 cookie,由此这个时候客户端的 cookie 中放的就是 session ID 键值对中。但是这个 session ID = 后面的这个 hash 值就会被变成为会话级存储,就会通知浏览器,把这个cookie 值进行保存。但浏览器关闭立即销毁。这个 session ID 的东西浏览器没有关闭就会一直存在。但是当浏览器关闭就会立即消失(因为这个东西只是在内存中存储)。

这种东西是由浏览器里面的代码决定他的生命周期,如果过去浏览器就会替用户删除,如果是会话级的 cookie 就会由浏览器在关闭的时候清理的干干净净的,不会落地。(这些事都是浏览器干的) 所以客户端是信不得的 Session是在动态服务器端(因为静态服务器没有session)

Session 可以在内存中开辟出一个空间,专门去生成这个东西,并保存这些值(这个值是在服务器端保存的)但是服务器端并不会把 session 中的所有值都发给用户,只是将 session 的 id 发个用户

5.1.3 Cookie 和 Session 关系图文解析

如图:当 c1(客户端)第一次访问服务器时,它是没有携带 cookie 的,然后通过浏览器进行了一些操作,这些操作也就生成了一些对应的键值对,服务器端就将这些键值对保存在内存中,叫做 session,而且生成了一个session id(一串hash值),通过响应报文,将这个 session id 传给客户端。 然后如果用户在 session id 未过期,或者没有关闭浏览器的情况下,再次访问服务器端时,请求报文会加上这个 session id ,然后服务器端匹配到这个 session id 后。这个 session id 所对应的 session 里面的键值对,就可以给客户端使用了。由于客户端的cookie 绑定是会话级的所以服务器端就会 session 超时时长链接,所谓超时时长是假如用户睡着了忘记了关网页,但是又是会话级的绑定,而且这个 session 会占用内存,所以为了释放内存服务器端就会有超时时常,当这次访问到达了超时时常服务器端就会自动断开会话从而释放内存(为啥服务器端的内存也是有限的)

5.1.4 负载均衡高可用实现 session id 共享的工作逻辑

当客户端第一次去访问 web 服务得时候调度到后端得 T1 主机上会得到一个 session id 。

但是当客户端又一次去访问 web 服务这次就被调度到了 T2 主机上,这次的 session id 就会发生变化(因为在T2主机上没有该 session id 所以就会重新生成一个新得 session id )而且这次客户端在访问得时候又一次被调派到了 T1 主机上因为没有该 session id 所以T1主机又会再次生成,但是这样做的后果是客户端无法保存自己的数据,因此我们为了避免这种情况的发生就要实现 session 共享,让后端多台服务器主机都有该 session id,我们就可用通过负载均衡技术和 session 共享技术来实现。

负载均衡是为了后端服务器得高可用

Session 共享是为了实现让后端多个负载均衡主机上保存该 session id 以避免用户得 session 变化而造成数据丢失。一直会让用户登陆。

5.1.4 Session id 会话保存方式(三种方法)

5.1.4.1 session sticky会话黏性(这是源地址hash,不推荐)

Session绑定

  • nginx:source ip

  • HAProxy:cookie

  • 优点:简单易配置

  • 缺点:如果目标服务器故障后,并且没有做 sessoin 持久化,就会丢失 session

5.1.4.2 session 复制集群(小企业中推荐)

Tomcat自己的提供的多播集群,通过多播将任何一台的session同步到其它节点。

缺点:

  • Tomcat 的同步节点不宜过多,互相及时通信同步 session 需要太多带宽,内部带宽消耗极大

  • 每一台都拥有全部 session,内存损耗太多

5.1.4.3 session server(大企业中推荐)

session 共享服务器,使用 memcached(内存服务)、redis(内存服务)做共享的 Session 服务器。

5.2 session 实现操作范例:

5.2.1 规划

IP 主机名 服务
10.0.0.7 A7 调度器 Nginx、HTTPD
10.0.0.17 B7 tomcat 1 JDK8、Tomcat 10.0.0-M9
10.0.0.27 C7 tomcat 2 JDK8、Tomcat 10.0.0-M9

5.2.2 操作流程

5.2.2.1 准备阶段

1、每台主机的域名解析

# A7 上配置主机名解析
[10:51:46 root@a7 ~]#vim /etc/hosts
10.0.0.7 t0.magedu.com
10.0.0.17 t1.magedu.com
10.0.0.27 t2.magedu.com

# B7上配置主机名解析
[10:51:47 root@B7 ~]#vim /etc/hosts
10.0.0.7 t0.magedu.com
10.0.0.17 t1.magedu.com
10.0.0.27 t2.magedu.com

# C7 上配置主机名解析
[10:51:50 root@C7 ~]#vim /etc/hosts
10.0.0.7 t0.magedu.com
10.0.0.17 t1.magedu.com
10.0.0.27 t2.magedu.com

2、将这几个主机得域名添加到 windows 系统中的 hosts 文件中

C:\Windows\System32\drivers\etc

5.2.3 nginx 反向代理负载均衡实现 tomcat 的 session 绑定

A7主机上配置 nginx 在 B7 和 C7 主机上配置 tomcat

5.2.3.1 A7 主机上使用 yum 下载 nginx

# 安装 epel 源
[10:54:05 root@a7 ~]#yum install epel-release.noarch -y

# 安装 nginx
[11:02:30 root@a7 ~]#yum install  nginx -y 

5.2.3.2 在 B7、C7 主机上安装 tomcat

1、将 jdk rpm 包上传到 B7 和 C7 主机上

B7 服务器上传 JDK 安装包

C7 服务器上传 JDK 安装

# B7 服务器上已上传
[10:54:28 root@B7 ~]#ll jdk-8u271-linux-x64.rpm 
-rw-r--r-- 1 root root 112994496 Nov  3 11:05 jdk-8u271-linux-x64.rpm

# C7 服务器上已上传
[10:54:47 root@C7 ~]#ll jdk-8u271-linux-x64.rpm 
-rw-r--r-- 1 root root 112994496 Nov  3 11:06 jdk-8u271-linux-x64.rpm

2、B7 、C7 服务器分别安装 jdk

# B7 上安装
[11:06:38 root@B7 ~]#yum install jdk-8u271-linux-x64.rpm -y

# C7 上安装
[11:06:38 root@C7 ~]#yum install jdk-8u271-linux-x64.rpm -y

3、生成环境变量配置

# B7 服务器上生成
[11:08:05 root@B7 ~]#echo 'export JAVA_HOME=/usr/java/latest' > /etc/profile.d/jdk.sh
[11:08:45 root@B7 ~]#echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile.d/jdk.sh
[11:08:51 root@B7 ~]#. /etc/profile.d/jdk.sh

# C7 服务器上生成
[11:08:41 root@C7 ~]#echo 'export JAVA_HOME=/usr/java/latest' > /etc/profile.d/jdk.sh
[11:09:12 root@C7 ~]#echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile.d/jdk.sh
[11:09:15 root@C7 ~]#. /etc/profile.d/jdk.sh

4、解决好了 jdk 环境之后现在就开始在 B7 和 C7 主机上编译安装 tomcat

# B7 上下载安装
[11:08:55 root@B7 ~]#wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-10/v10.0.0-M9/bin/apache-tomcat-10.0.0-M9.tar.gz

# C7 上下载安装
[11:09:18 root@C7 ~]#wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-10/v10.0.0-M9/bin/apache-tomcat-10.0.0-M9.tar.gz

5、创建一个 /app 的目录然后指定将 tomcat 解压到该目录中

# B7 上创建并解压
[14:29:02 root@B7 ~]#mkdir -pv /app
[14:33:54 root@B7 ~]#tar xf apache-tomcat-10.0.0-M9.tar.gz -C /app

# C7 上创建
[14:34:00 root@C7 ~]#mkdir -pv /app
[14:34:10 root@C7 ~]#tar xf apache-tomcat-10.0.0-M9.tar.gz -C /app

6、进入到 /app 目录中给 tomcat 创建一个软连接

# B7 服务器上进入到 /app   目录中给 tomcat 创建软连接
[14:35:12 root@B7 ~]#cd /app
[14:37:16 root@B7 app]#ln -sv apache-tomcat-10.0.0-M9/ tomcat

# C7 服务器上进入到 /app 目录中给 tomcat 创建一个软连接
[14:35:25 root@C7 ~]#cd /app
[14:36:39 root@C7 app]#ln -sv apache-tomcat-10.0.0-M9/ tomcat

7、创建 tomcat 用户,并授权

# B7 上创建
[14:37:24 root@B7 app]#useradd tomcat -r
[14:38:43 root@B7 app]#chown -R tomcat.tomcat /app/apache-tomcat-10.0.0-M9/

# C7 上创建
[14:37:24 root@BC app]#useradd tomcat -r
[14:38:48 root@C7 app]#chown -R tomcat.tomcat /app/apache-tomcat-10.0.0-M9/

8、设置 tomcat 环境变量,并使其生效

# B7 服务器上设置
[14:39:39 root@B7 app]#vim /etc/profile.d/tomcat.sh
#!/bin/bash
export CATALINA_HOME=/app/tomcat
export PATH=$CATALINA_HOME/bin:/$PATH
# 使其生效
[14:48:28 root@B7 app]#. /etc/profile.d/tomcat.sh 


# C7 服务器上设置
[14:39:47 root@C7 app]#vim /etc/profil.d/tomcat.sh
#!/bin/bash
export CATALINA_HOME=/app/tomcat
export PATH=$CATALINA_HOME/bin:/$PATH
# 使其生效
[14:48:28 root@C7 app]#. /etc/profile.d/tomcat.sh 

9、通过查看 tomcat 版本信息我们已经实现安装

# 在 B7 上观察已经实现安装
[14:48:35 root@B7 app]#/app/tomcat/bin/version.sh 
Using CATALINA_BASE:   /app/tomcat
Using CATALINA_HOME:   /app/tomcat
Using CATALINA_TMPDIR: /app/tomcat/temp
Using JRE_HOME:        /usr/java/latest
Using CLASSPATH:       /app/tomcat/bin/bootstrap.jar:/app/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:   
Server version: Apache Tomcat/10.0.0-M9         # 版本为 Tomcat/10.0.0-M9 已经实现安装

# 在 C7 上观察已经安装好了
[14:48:40 root@C7 app]#/app/tomcat/bin/version.sh
Using CATALINA_BASE:   /app/tomcat
Using CATALINA_HOME:   /app/tomcat
Using CATALINA_TMPDIR: /app/tomcat/temp
Using JRE_HOME:        /usr/java/latest
Using CLASSPATH:       /app/tomcat/bin/bootstrap.jar:/app/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:   
Server version: Apache Tomcat/10.0.0-M9         # 版本为 Tomcat/10.0.0-M9 已经实现安装

现在 tomcat 已经部署完毕,接下来我们就要给它创建项目了

5.2.3.3 在 B7、C7 服务器上创建项目

1、创建项目路径

# B7 上创建项目路径
[14:49:19 root@B7 app]#mkdir -pv /data/webapps/ROOT

# C7 上创建项目路径
[14:50:33 root@C7 app]#mkdir -pv /data/webapps/ROOT

2、B7 和 C7 主机上写两个 web 页面,这个是测试我们的 session 值得显示页面和参数,是一个 jsp 后缀文件。

# B7 上编写一个 index.jsp 文件
[15:07:05 root@B7 app]#vim /data/webapps/ROOT/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>

# C7 上编写一个 index.jsp 文件
[15:11:08 root@C7 app]#vim /data/webapps/ROOT/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>

3、B7、C7 启动 tomcat

# B7 启动 tomcat
[15:41:43 root@B7 app]#/app/tomcat/bin/startup.sh 

# C7 启动 tomcat
[15:42:33 root@C7 app]#/app/tomcat/bin/startup.sh

4、修改 B7 和 C7 主机的 tomcat 主配置文件再分别将两个主机得主机头和主机头的项目目录添加进去,并且再引擎中也要将该主机头的域名添加进去

# B7 上修改配置文件
[15:42:54 root@B7 app]#vim /app/tomcat/conf/server.xml 
    <Engine name="Catalina" defaultHost="t1.magedu.com">
      <Host name="t1.magedu.com"  appBase="/data/webapps"
            unpackWARs="true" autoDeploy="true">
          
# C7 上修改配置文件
[15:43:17 root@C7 app]#vim /app/tomcat/conf/server.xml 
    <Engine name="Catalina" defaultHost="t2.magedu.com"> # 将这里修改为 C7 主机对应的主机头 t2.magedu.com
      <Host name="t2.magedu.com"  appBase="/data/webapps"
            unpackWARs="true" autoDeploy="true">

5、停止 tomca 服务再重启 tomcat 服务使其配置文件生效。

# B7 上操作
[15:49:01 root@B7 app]#/app/tomcat/bin/shutdown.sh              # 停止 tomcat
[15:51:13 root@B7 app]#/app/tomcat/bin/startup.sh               # 启动 tomcat

# C7 上操作
[15:51:01 root@C7 app]#/app/tomcat/bin/shutdown.sh              # 停止 tomcat
[15:51:21 root@C7 app]#/app/tomcat/bin/startup.sh               # 启动 tomcat

6、浏览器分别访问 B7 和 C7 主机的 8080 端口就已经配置好了

访问 B7 服务器:http://t1.magedu.com:8080/

访问 C7 服务器:http://t2.magedu.com:8080/

5.2.3.4 回到 A7 nginx 服务器上配置调度均衡反向代理

现在又到 A7 主机上去修改 nginx 得子配置文件,让 nginx 支持调度均衡和反向代理

1、修改配置文件。 支持调度均衡和反向代理

[11:03:00 root@a7 ~]#vim /etc/nginx/conf.d/tomcat.conf
upstream tomcats {
    server t1.magedu.com:8080;
    server t2.magedu.com:8080;
    }
server {
    server_name t0.magedu.com;
    listen 80;
location / {
    proxy_pass http://tomcats;
    }
}

2、 检查 nginx 语法并重启 nginx

# 检查语法
[16:45:53 root@a7 ~]#nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

# 重启 tomcat
[16:45:56 root@a7 ~]#systemctl restart nginx

3、浏览器测试,nginx 是否实现反向代理

多次刷新发现后端 tomcat 主机已经实现了负载均衡。

现在就有了 sessionid 和户端 tomcat 得主机 ip。但是由于是负载均衡所以会来回再两个 tomcat 主机上来回跳动而 sessionid 就无法保存,所以这个时候我们就要配置它的 session ID 绑定

5.2.3.5 在 A7 服务器上开启 nginx session ID 绑定

1、修改 nginx 配置文件,开启 ip_hash绑定

[16:46:18 root@a7 ~]#vim /etc/nginx/conf.d/tomcat.conf
upstream tomcats {
    server t1.magedu.com:8080;
    server t2.magedu.com:8080;
ip_hash;                        # 添加这行
    }
server {
    server_name t0.magedu.com;
    listen 80;
location / {
    proxy_pass http://tomcats;
    }
}

2、 重启 nginx

[16:50:59 root@a7 ~]#systemctl restart nginx

5.2.3.6 测试访问 nginx 已经实现 session id 绑定

通过浏览器分别访问两次,然后通过时间来观察会发现两次的时间都不同,但是 session id 和 ip 依旧是相同的从而实现了 session 的绑定

第一次访问:

第二次访问:

然后我们再次刷新只会发现时间变了而后端 tomcat 主机 ip 和 sessionID 没有任何变化。从而实现了session绑定

点赞