一文搞定动静分离、负载均衡、Session共享

一、基础概念

1-1、什么是 Nginx

Nginx 是一个具有高性能的 HTTP 和反向代理服务器。它能支持 5万 个并发连接,而且 CPU 跟内存占用也非常低。低至 1万 个没有活动的链接仅占用 2.5M 的内存。

1-2、什么是正向代理

举个例子:正向代理可以理解为我们点外卖的某种方式。比如,我们想吃狗剩儿家的驴不理馅饼,然后我们打开外卖软件,点了狗剩儿家的驴不理馅饼,并且外卖小哥也很快把馅饼送到了你手里。这个过程就是正向代理。

正向代理通俗的来说就是,我们通过代理服务器间接的去访问已知的目标服务器。

1-3、什么是反向代理

还拿点外卖举例子:某天凌晨两点,你突然做了个饿梦,醒了。你坐在床上忽然很想吃饺子。但是这个时候你不知道哪里还有卖饺子的地方,于是你叫了个跑腿,让跑腿小哥帮你买两份饺子回来,哪家店的不重要,只要是饺子就行。很快你吃上了热气腾腾的饺子。这个过程就叫方向代理。

反向代理通俗来说就是,我们只知道自己想要访问的是页面是什么,至于是集群中的哪台服务器提供的服务,我们并不知道也不关心。

1-4、什么是负载均衡

举个例子:有一天我们去理发,店里有五个 Tony 同时工作,但由于人很多,我们还是需要拿了号在等候区等着被叫号。这种通过某种规则或方式进行任务分发的过程就叫负载均衡。负载均衡主要用于解决高负载的问题。

1-5、什么是动静分离

举个例子:你去一家餐馆吃饭,店里售卖各种成品饮料和现做的主食,但是只有老板一人在服务客人。于是店里人多的时候,老板又得给顾客拿饮料,有得给顾客炒菜,忙得不知所措,顾客的体验也很差。毕竟有的顾客只是需要喝杯饮料而已,却不能得到及时满足。——这就是未实现动静分离的情况。

第二天你去了另一家餐馆吃饭,这个店里有两个服务生,一个负责售卖成品饮料,一个负责现场制作客人点的主食。此时有点口渴的你点了一杯饮料,而且负责饮品的服务生很快就拿给了你。你表示很满意。——这就是动静分离的情况。

回到软件层面总结一下就是:由于 TomcatServlet 容器,对高并发的支持并不理想。所以,如果我们将前端页面和 Servlet 都交由 Tomcat 处理的话,服务器的响应效率是不高的。所以就需要把前端静态页面相关的内容交由 Nginx 去做处理,以提高静态资源的响应速度,同时还降低了 Servlet 动态资源服务器的压力。

1-6、什么是 Session 一致性

现实工作场景中,为了解决高并发的问题,我们的项目通常会进行集群部署。这个时候就会随之而来一个问题:当同一有效用户的不同请求交由集群中的不同服务器来做处理时,由于 Session 没有在同一台机器上,就导致了信息丢失或不一致。最常见的现象就是用户需要重新登录,以获取对应资源的访问权限。面对这个问题我们通常需要做相应的服务器配置或对 Session 进行统一管理,以保证本次会话过程中的的 Session 信息始终保持一致。

二、环境准备

  • CentOS 7.6.1810 Minimal(CentOS 最小化安装包)
  • nginx-1.18.0.tar.gz(用作:负载均衡服务器、反向代理服务器、静态资源服务器)
  • jdk-8u144-linux-x64.tar.gz(JDKJava Developer Killer
  • apache-tomcat-8.5.41.tar.gz(用作:Servlet 服务)
  • redis-6.0.6.tar.gz(用作:Session 缓存服务器)

三、环境搭建

注意:本文在 Linux 系统中进行软件安装时,使用的用户为 root 管理员用户。

3-1、巧用 VM

通过 VM 安装 Linux 虚拟机算不上难,但是相关配置一波操作下来也会消耗咱们一些宝贵的时间。 对博主而言,面对生活的重压,能有自己的时间用来学习就显得弥足珍贵。可不巧的是,博主不仅菜,手还贼欠,喜欢在未知的领域探(瞎)索(搞)。有时脑袋遭雷劈后秀一把 rm -rf /,瞬间GG。这个时候咋办?再重新安装一遍?不太好吧?那这个问题该怎么更好的解决呢?

解决方式很简单:

  1. 创建母机。当我们在 VM 中完成了一台虚拟机的创建和基础配置后,则立即将该虚拟机关机(注意:这里说的是关闭当前创建的虚拟机,不是关闭 VM 软件)。从此以后,这个包含基础配置的虚拟就不必再打开了,让它安安静静的做只“母机”就好了。
  2. 克隆母机。完成关机后,选中母机右键菜单中选择 管理克隆,然后一路 下一步 到如下页面,填写相关信息后点击 完成 即可秒级创建一台已完成基础配置的新虚拟机。从此再也不怕因为秀操作把系统搞坏了~!

在这里插入图片描述

  1. 后台运行。完成母机克隆后我们就可以开开心心的使用了。这时,你是不是顺手就点了右上角的最小化?嗯?其实还有个更好的方式可以选的,你可以点右上角的 X,然后选择 在后台运行(B) ,让它成为一个后台服务,以此来减少 VM 对本机内存的占用。

在这里插入图片描述

  1. 虚拟机中的系统用完之后可选择 挂起 来保存当前状态。这样一来,下次打开系统时可无缝衔接上次关机前的操作。(别笑,我还真见过在虚拟机里 开始关机 的老程序猿)

在这里插入图片描述

3-2、redis 安装

  1. 上传并解压 redis-6.0.6.tar.gz
  2. yum -y install centos-release-scl
  3. yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
  4. echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
  5. source /etc/profile
  6. 进入 redis-6.0.6 目录中执行 make
  7. redis 安装进指定目录中 make install PREFIX=/usr/local/redis-6.0.6
  8. 从解压后的 redis-6.0.6 包中拷贝 redis.conf 文件至 /usr/local/redis-6.0.6 目录中
  9. 修改刚刚拷贝的 /usr/local/redis-6.0.6 目录下的 redis.conf 文件
 # bind 127.0.0.1 (注释掉改参数以供其他机器访问当前 redis)
 protected-mode no (将原本的 yes 改为 no,以供其他机器访问当前 redis)
 daemonize yes (将原本的 no 改为 yes,使 redis 以后台服务模式启动)
  1. 启动 redis 并制定配置文件,进入/usr/local/redis-6.0.6/bin 目录,执行如下命令
./redis-server config/redis.conf
  1. 测试 redis:进入/usr/local/redis-6.0.6/bin 目录,执行 ./redis-cli 进入连接,则表示启动成功,相反则表示启动失败。
  2. 关闭 redis,进入/usr/local/redis-6.0.6/bin 目录,执行如下命令
./redis-cli
shutdown

Tips

  • 使用 make 命令时报错:致命错误:jemalloc/jemalloc.h:没有那个文件或目录

    解决办法:make MALLOC=libc

  • 使用 make 命令时报下图错误

在这里插入图片描述
这是因为系统中 gcc 库版本过低,编译 redis-6.x,要求 5.3 以上的编译器。执行如命令即可修复。

1. yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
2. echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
3. source /etc/profile

3-3、Nginx 安装

  1. 上传并解压 nginx-1.18.0.tar.gz
  2. 进入 nginx-1.18.0 执行 ./configure
  3. 执行 make
  4. 执行 make install
  5. 启动 Nginx:进入 /usr/local/nginx/sbin 执行 ./nginx
  6. 重启/重新加载配置文件:进入 /usr/local/nginx/sbin 执行 ./nginx -s reload
  7. 测试 Nginx:浏览器访问 nginx 所在服务器 IP,看见下图则表示启动成功。

在这里插入图片描述

  1. 关闭 Nginx:进入 /usr/local/nginx/sbin 执行 ./nginx -s stop
    杀进程方式:
ps -aux|grep nginx (找到 nginx 进程 ID)
kill -9 17352 17386 (强行结束 nginx 进程)

在这里插入图片描述

Tips

  • 使用 make 命令时报错: 没有规则可以创建“default”需要的目标“build”。 停止。

    解决办法:yum install pcre-devel zlib zlib-devel openssl openssl-devel

3-4、JDK 安装

  1. 上传并解压 jdk-8u144-linux-x64.tar.gz
  2. 添加 Java 环境变量
vim /etc/profile
# 最后一行添加如下内容  注意修改为自己的 JDK 地址
export JAVA_HOME=/usr/local/jdk1.8.0_144
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  1. 使修改后的环境变量生效 source /etc/profile (仅作用于当前用户)
  2. 测试:java -version

3-5、Tomcat 启动

  1. 上传并解压 apache-tomcat-8.5.41.tar.gz
  2. 启动:进入 apache-tomcat-8.5.41\bin 目录执行 ./shartup.sh
  3. 测试:浏览器访问 tomcat 所在服务 IP:8080
  4. 关闭:进入 apache-tomcat-8.5.41\bin 目录执行 ./shutdown.sh

3-5、开放端口

如果你的程序在虚拟机中启动了,却在实体机无法访问,则需考虑 Linux 系统中所用软件是否被防火墙屏蔽了。添加防火墙白名单方式如下:

  1. systemctl start firewalld.service
  2. firewall-cmd --zone=public --add-port=8080/tcp --permanent
  3. systemctl restart firewalld.service
  4. firewall-cmd --reload

Tips

  • 启动防火墙报错:Failed to start firewalld.service: Unit is masked.

    解决办法:systemctl unmask firewalld

四、动静分离

动静分离的相关概念在本文的第一部分已经做了相关阐述,这一部分,我们就如何实现动静分离来做进一步的阐述。来到这一步,说明上面的软件部署已经走通了,TomcatNginx 均可提供相关服务。现在我们需要做的就是,把 TomcatNginx 结合起来。

  1. 配置 Nginx 反向代理。进入 /usr/local/nginx/conf 目录,vim 打开 nginx.conf 文件。
  2. http 节点下添加如下内容(注意替换里面的 IP 和端口)
upstream loadBalance {
	server 192.168.32.102:8080;
}
  1. http 节点下的 server 节点内添加如下内容
location /tomcat/service {
	proxy_pass http://loadBalance/;
}
  1. 重新加载 Nginx 配置文件。进入 /usr/local/nginx/sbin 执行 ./nginx -s reload
  2. 浏览器访问 http://192.168.32.102/tomcat/service

当当当当……惊喜不惊喜,意外不意外。访问的页面竟然没有样式,哈哈哈~!!!不过别慌,在浏览器中按下 F12,刷新一下页面看看网络资源请求状态。

在这里插入图片描述
是不是发现我们的 Nginx 反向代理了 Tomcat 服务器后,静态资源找不到了?那么这个问题该怎么解决?

  1. 进入 /usr/local/nginx ,创建存放静态资源的文件夹。mkdir staticData
  2. Tomcatwebapps 文件夹下的 ROOT 文件夹整个拷贝至 staticData 文件夹下并改名为 tomcat
  3. http 节点下的 server 节点内添加如下静态资源服务配置。
location /tomcat {
	root staticData;
}
  1. 重新加载 Nginx 配置文件。进入 /usr/local/nginx/sbin 执行 ./nginx -s reload
  2. 再次访问:http://192.168.32.102/tomcat/service (页面没问题了吧,skr~!!!)

到这里我们就完成了 Nginx + Tomcat 的动静分离。动的是 Tomcat 中的 index.jsp,静的是页面样式及图片。

Tips

如果以上内容都配置正确,访问静态资源却报 403 错误,则需检查 staticData/tomcat 文件夹下资源的权限是否放开。文件权限修改命令:chmod 777 *

五、负载均衡

完成了上一步的动静分离,实现这一步的负载均衡就简单多了,我们只要启动一个其他端口的 Tomcat,然后再像 Nginx 配置文件中添加一行代码就能实现轮询策略下的负载均衡。

  1. 找到之前配置在 nginx.conf 文件中的 upstream loadBalance 节点,在该节点内添加
server 192.168.32.102:8081;
  1. 重新加载 Nginx 配置文件。进入 /usr/local/nginx/sbin 执行 ./nginx -s reload
  2. 再次访问:http://192.168.32.102/tomcat/service

在这里插入图片描述
Tips

由于两台 Tomcat 服务器页面显示的内容相同,所以我们并不能发现变化。因此可以在不同 Tomcat 中的 index.jsp 添加不同内容,以示区分。

六、Session 一致性

这个功能的实现博主这里是写了一个简单的用户登录验证的 Demo,通过获取放在 Session 中的 username 属性,来判断是否登录。

6-1、关键代码

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

application.properties

#jsp 支持
spring.mvc.view.suffix=.jsp
spring.mvc.view.prefix=/WEB-INF/jsp/

#关闭默认模板引擎
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=false

spring.redis.database=0
spring.redis.host=192.168.32.102
spring.redis.port=6379
spring.redis.connect-timeout=5000

RequestInterceptor.java

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 请求权限验证
 */
public class RequestInterceptor implements HandlerInterceptor {
    
    

    /**
     * 之前执行(进入Handler处理之前)
     * 可以进行权限验证
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
    
    
        HttpSession session = request.getSession();
        // 屏蔽来自浏览器默认发送的网站图标请求的干扰
        if ("/favicon.ico".equals(request.getRequestURI())){
    
    
            return false;
        }
        System.out.println("RequestURI:" + request.getRequestURI()
                + "\r\nSessionID:" + session.getId());

        Object username = session.getAttribute("username");
        if (username == null) {
    
    
            // 没有登录,重定向到登录页
            System.out.println("未登录,请登录");
            response.sendRedirect(request.getContextPath() + "/login/toLogin");
            return false;
        } else {
    
    
            System.out.println("已登录,放行请求");
            // 已登录,放行
            return true;
        }
    }

    /**
     * 之中执行(Handler处理完毕但尚未跳转页面)
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {
    
    
    }

    /**
     * 之后执行(Handler处理完毕而且已经跳转页面)
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
    
    
    }
}

BalanceApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
@EnableRedisHttpSession
public class BalanceApplication extends SpringBootServletInitializer {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(BalanceApplication.class, args);
    }
}

Tips

  • 在完成 pom.xml 文件的 spring-session 相关依赖配置后,@EnableRedisHttpSession 注解即使不添加也可以实现,redisSession 缓存的统一管理。

6-2、部署与配置

完成上述代码的打包后,将项目分别部署在由 Nginx 管理的两台 Tomcat 中。然后在 nginx.conf 配置文件中的 http 节点下的 server 节点内添加如下内容。

location /balance {
	proxy_pass http://loadBalance/balance;
}

进入 /usr/local/nginx/sbin 执行 ./nginx -s reload 重新加载 Nginx 配置文件。最后就可以访问了~!

http://192.168.32.102/balance (用户名密码都是 admin)

Tips

其实这一步的完成并没有让你感受到 Session 一致性的问题,所以你可以先把 6-1 中的相关依赖和 @EnableRedisHttpSession 注解注释掉,再部署访问一次。这次定会有不一样的感受。

七、源码

源码下载

-------------------- 披上铠甲手握长枪,用尽所有,捍卫所追求的光明与温柔。 --------------------

猜你喜欢

转载自blog.csdn.net/Supreme_Sir/article/details/111311056