一、基础概念
1-1、什么是 Nginx
Nginx
是一个具有高性能的 HTTP
和反向代理服务器。它能支持 5万
个并发连接,而且 CPU
跟内存占用也非常低。低至 1万
个没有活动的链接仅占用 2.5M
的内存。
1-2、什么是正向代理
举个例子:正向代理可以理解为我们点外卖的某种方式。比如,我们想吃狗剩儿家的驴不理馅饼,然后我们打开外卖软件,点了狗剩儿家的驴不理馅饼,并且外卖小哥也很快把馅饼送到了你手里。这个过程就是正向代理。
正向代理通俗的来说就是,我们通过代理服务器间接的去访问已知的目标服务器。
1-3、什么是反向代理
还拿点外卖举例子:某天凌晨两点,你突然做了个饿梦,醒了。你坐在床上忽然很想吃饺子。但是这个时候你不知道哪里还有卖饺子的地方,于是你叫了个跑腿,让跑腿小哥帮你买两份饺子回来,哪家店的不重要,只要是饺子就行。很快你吃上了热气腾腾的饺子。这个过程就叫方向代理。
反向代理通俗来说就是,我们只知道自己想要访问的是页面是什么,至于是集群中的哪台服务器提供的服务,我们并不知道也不关心。
1-4、什么是负载均衡
举个例子:有一天我们去理发,店里有五个 Tony
同时工作,但由于人很多,我们还是需要拿了号在等候区等着被叫号。这种通过某种规则或方式进行任务分发的过程就叫负载均衡。负载均衡主要用于解决高负载的问题。
1-5、什么是动静分离
举个例子:你去一家餐馆吃饭,店里售卖各种成品饮料和现做的主食,但是只有老板一人在服务客人。于是店里人多的时候,老板又得给顾客拿饮料,有得给顾客炒菜,忙得不知所措,顾客的体验也很差。毕竟有的顾客只是需要喝杯饮料而已,却不能得到及时满足。——这就是未实现动静分离的情况。
第二天你去了另一家餐馆吃饭,这个店里有两个服务生,一个负责售卖成品饮料,一个负责现场制作客人点的主食。此时有点口渴的你点了一杯饮料,而且负责饮品的服务生很快就拿给了你。你表示很满意。——这就是动静分离的情况。
回到软件层面总结一下就是:由于 Tomcat
是 Servlet
容器,对高并发的支持并不理想。所以,如果我们将前端页面和 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(
JDK
:Java 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。这个时候咋办?再重新安装一遍?不太好吧?那这个问题该怎么更好的解决呢?
解决方式很简单:
- 创建母机。当我们在
VM
中完成了一台虚拟机的创建和基础配置后,则立即将该虚拟机关机(注意:这里说的是关闭当前创建的虚拟机,不是关闭VM
软件)。从此以后,这个包含基础配置的虚拟就不必再打开了,让它安安静静的做只“母机”就好了。 - 克隆母机。完成关机后,选中母机右键菜单中选择
管理
→克隆
,然后一路下一步
到如下页面,填写相关信息后点击完成
即可秒级创建一台已完成基础配置的新虚拟机。从此再也不怕因为秀操作把系统搞坏了~!
- 后台运行。完成母机克隆后我们就可以开开心心的使用了。这时,你是不是顺手就点了右上角的最小化?嗯?其实还有个更好的方式可以选的,你可以点右上角的
X
,然后选择在后台运行(B)
,让它成为一个后台服务,以此来减少VM
对本机内存的占用。
- 虚拟机中的系统用完之后可选择
挂起
来保存当前状态。这样一来,下次打开系统时可无缝衔接上次关机前的操作。(别笑,我还真见过在虚拟机里开始
→关机
的老程序猿)
3-2、redis 安装
- 上传并解压
redis-6.0.6.tar.gz
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
source /etc/profile
- 进入
redis-6.0.6
目录中执行make
- 把
redis
安装进指定目录中make install PREFIX=/usr/local/redis-6.0.6
- 从解压后的
redis-6.0.6
包中拷贝redis.conf
文件至/usr/local/redis-6.0.6
目录中 - 修改刚刚拷贝的
/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 以后台服务模式启动)
- 启动
redis
并制定配置文件,进入/usr/local/redis-6.0.6/bin
目录,执行如下命令
./redis-server config/redis.conf
- 测试
redis
:进入/usr/local/redis-6.0.6/bin
目录,执行./redis-cli
进入连接,则表示启动成功,相反则表示启动失败。 - 关闭
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 安装
- 上传并解压
nginx-1.18.0.tar.gz
- 进入
nginx-1.18.0
执行./configure
- 执行
make
- 执行
make install
- 启动
Nginx
:进入/usr/local/nginx/sbin
执行./nginx
- 重启/重新加载配置文件:进入
/usr/local/nginx/sbin
执行./nginx -s reload
- 测试
Nginx
:浏览器访问nginx
所在服务器IP
,看见下图则表示启动成功。
- 关闭
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 安装
- 上传并解压
jdk-8u144-linux-x64.tar.gz
- 添加
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
- 使修改后的环境变量生效
source /etc/profile
(仅作用于当前用户) - 测试:
java -version
3-5、Tomcat 启动
- 上传并解压
apache-tomcat-8.5.41.tar.gz
- 启动:进入
apache-tomcat-8.5.41\bin
目录执行./shartup.sh
- 测试:浏览器访问
tomcat
所在服务IP:8080
- 关闭:进入
apache-tomcat-8.5.41\bin
目录执行./shutdown.sh
3-5、开放端口
如果你的程序在虚拟机中启动了,却在实体机无法访问,则需考虑 Linux
系统中所用软件是否被防火墙屏蔽了。添加防火墙白名单方式如下:
systemctl start firewalld.service
firewall-cmd --zone=public --add-port=8080/tcp --permanent
systemctl restart firewalld.service
firewall-cmd --reload
Tips:
-
启动防火墙报错:Failed to start firewalld.service: Unit is masked.
解决办法:
systemctl unmask firewalld
四、动静分离
动静分离的相关概念在本文的第一部分已经做了相关阐述,这一部分,我们就如何实现动静分离来做进一步的阐述。来到这一步,说明上面的软件部署已经走通了,Tomcat
和 Nginx
均可提供相关服务。现在我们需要做的就是,把 Tomcat
和 Nginx
结合起来。
- 配置
Nginx
反向代理。进入/usr/local/nginx/conf
目录,vim
打开nginx.conf
文件。 - 在
http
节点下添加如下内容(注意替换里面的IP
和端口)
upstream loadBalance {
server 192.168.32.102:8080;
}
- 在
http
节点下的server
节点内添加如下内容
location /tomcat/service {
proxy_pass http://loadBalance/;
}
- 重新加载
Nginx
配置文件。进入/usr/local/nginx/sbin
执行./nginx -s reload
- 浏览器访问
http://192.168.32.102/tomcat/service
当当当当……惊喜不惊喜,意外不意外。访问的页面竟然没有样式,哈哈哈~!!!不过别慌,在浏览器中按下 F12
,刷新一下页面看看网络资源请求状态。
是不是发现我们的 Nginx
反向代理了 Tomcat
服务器后,静态资源找不到了?那么这个问题该怎么解决?
- 进入
/usr/local/nginx
,创建存放静态资源的文件夹。mkdir staticData
- 将
Tomcat
中webapps
文件夹下的ROOT
文件夹整个拷贝至staticData
文件夹下并改名为tomcat
- 在
http
节点下的server
节点内添加如下静态资源服务配置。
location /tomcat {
root staticData;
}
- 重新加载
Nginx
配置文件。进入/usr/local/nginx/sbin
执行./nginx -s reload
- 再次访问:
http://192.168.32.102/tomcat/service
(页面没问题了吧,skr~!!!)
到这里我们就完成了 Nginx
+ Tomcat
的动静分离。动的是 Tomcat
中的 index.jsp
,静的是页面样式及图片。
Tips:
如果以上内容都配置正确,访问静态资源却报 403
错误,则需检查 staticData/tomcat
文件夹下资源的权限是否放开。文件权限修改命令:chmod 777 *
五、负载均衡
完成了上一步的动静分离,实现这一步的负载均衡就简单多了,我们只要启动一个其他端口的 Tomcat
,然后再像 Nginx
配置文件中添加一行代码就能实现轮询策略下的负载均衡。
- 找到之前配置在
nginx.conf
文件中的upstream loadBalance
节点,在该节点内添加
server 192.168.32.102:8081;
- 重新加载
Nginx
配置文件。进入/usr/local/nginx/sbin
执行./nginx -s reload
- 再次访问:
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
注解即使不添加也可以实现,redis
对Session
缓存的统一管理。
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
注解注释掉,再部署访问一次。这次定会有不一样的感受。
七、源码
-------------------- 披上铠甲手握长枪,用尽所有,捍卫所追求的光明与温柔。 --------------------