在上一篇博客中,tomcat性能调优,虽然我们对tomcat的内嵌配置进行了修改,但是单机容量的性能瓶颈仍然影响着整个项目的运行效率。在本篇博客中,在此基础上进行反向代理负载均衡的优化,深入讲解nginx高性能的原因,并且使用nginx作为动静分离的服务器部署,同时在项目中引入分布式会话管理的机制来解决登录一致态的问题。
在采用Jmeter进行性能压测,采用top -H命令可以查看CPU的使用率,memory占用增加,CPU中用户空间和系统调用的cpu使用情况,需要引入水平扩展方案;
水平扩展方案
将对应的服务端做一个对等的水平部署
简单的流程图如下图所示:
正向代理:
通常的代理服务器,客户端必须指定代理服务器,并且将直接要发送到web服务器上的http请求发送到代理服务器中,并且由代理服务器向Internet上的web服务器发送请求,最终达到客户机的目的;
反向代理(Reverse Proxy)
反向代理是指以代理服务器来接收Internet上的连接请求,然后将请求转发到内部网络上的服务器中,并将从服务器上返回的结果,返回给Internet请求连接的客户端,此时代理服务器对外表现为一个反向代理服务器;
Nginx只做请求的转发,后台有多个http服务器提供服务,nginx的功能就是把请求转发到后面的服务器,决定把请求发给谁;简单的来说,Nginx可以管理后端的一个tomcat集群,然后以一个统一的域名供用户去访问;
对大型网站而言,不管多么强大的服务器,都满足不了网站持续增长的业务需求。这种情况下,更恰当的做法是增加一台服务器分担原有服务器的访问及存储压力。扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性.
负载均衡(Load Balance)通过负载均衡调度服务器,将来自浏览器的访问请求分发到应用服务器集群中的任何一台服务器上,如果有更多的用户,就在集群中加入更多的应用服务器,使应用服务器的负载压力不再成为整个网站的瓶颈;
Nginx作为反向代理服务器,可以对后台的多台Tomcat服务器负载均衡;
Nginx负载均衡策略
通过vim conf/nginx.conf,命令进行负载均衡的配置;
upstream nginxDemo {
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
upstream nginxDemo {
least_conn;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
#服务器A和服务器B的访问比例为:2-1;比如有3个请求,前两个会访问A,三个访问B,其它规则和轮询一样。
upstream nginxDemo {
server 127.0.0.1:8081 weight=2; #服务器A
server 127.0.0.1:8082; #服务器B
}
upstream nginxDemo {
ip_hash;
server 127.0.0.1:8081 weight=2; #服务器A
server 127.0.0.1:8082; #服务器B
}
基于weight的负载均衡和基于ip_hash的负载均衡可以组合在一起使用;
upstream nginxDemo {
server 127.0.0.1:8081; #服务器A
server 127.0.0.1:8082; #服务器B
fair;
}
从阿里云购买四台相同的配置,按量付费:
nginx反向代理,秒杀应用服务器1,秒杀应用服务器2,秒杀数据库服务器;
安装部署Nginx
OpenResty将许多配置编译成了一个二进制包,内部集成了许多nginx特性;
在Nginx web服务器中:
nginx动静分离服务器
如何使用动态资源?
nginx做反向代理服务器
开启Jmeter压测
单机压测:
将服务器改成nginx反向代理服务器的ip,进行同样的压测
cpu消耗3%,平均耗时450,tps1700,解决了单机容量瓶颈问题;
Nginx高性能主要分为以下三点:
I/O多路复用就是通过一种机制,可以监视多个描述符,一旦某个IO能够读写,通知程序进行相应的读写操作。
I/O多路复用的场合
Java BIO模型
client和server之间通过tpc/ip建立联系,javaclient只有等到所有字节流socket.write到字节流的缓冲之后,对应的java client才会返回;若网络很慢,缓冲区填满之后,client就必须等待信息传输过去之后,使得缓冲区可以给上游去写时,才可达到直接返回的效果;
Linux select模型
Linux select模型,变更触发轮训查询,文件描述符有1024数量上限;一旦java server被唤醒,并且对应的socket连接打上有变化的标识之后,就代表已经有数据可以让你读写;
弊端:
轮询效率低,有1024数量限制;
epoll模型
epoll是为了解决select和poll的轮询方式效率低问题;
假设一个场景:
有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?
在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大。因此,select/poll一般只能处理几千的并发连接。
epoll的设计和实现与select完全不同:
epoll通过在Linux内核中申请一个简单的文件系统(文件系统一般由B+树实现)
把原先的select/poll调用分成了3个部分:
实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。
具体细节参考这边博文:epoll多路复用
Nginx多进程模型如下所示:
管理员理解为root操作用户,用于启动管理nginx进程;
Master进程的主要功能:
nginx会启动一个master进程,然后根据配置文件内的worker进程的数量去启动相应的数量的worker进程,master进程和worker进程是一个父子关系;master进程用来管理worker进程,worker进程才是用来管理客户端连接的。
Master进程会先创建好对应的socke去监听对应的短裤,然后再fork出多个worker进程,master会启动一个epoll的多路复用模型;当client想要在socket端口建立经典的TCP三次握手建立连接的时候,对应的epoll多路复用会产生一个回调,通知所有的可以accept的worker进程,但只有一个worker进程会成功,其它的都会失败。
Nginx提供了一把共享锁accept_mutex来保证同一时刻只有一个work进程在accept连接,从而解决惊群问题;当一个worker进程accept这个连接后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接;
一个线程可以有多个协程,协程是线程的内存模型
客户端用cookie保存了sessionID,当我们请求服务器的时候,会把这个sessionID一起发给服务器,服务器会到内存中搜索对应的sessionID,如果找到了对应的 sessionID,说明我们处于登录状态,有相应的权限;如果没有找到对应的sessionID,这说明:要么是我们把浏览器关掉了(后面会说明为什么),要么session超时了(没有请求服务器超过20分钟),session被服务器清除了,则服务器会给你分配一个新的sessionID。你得重新登录并把这个新的sessionID保存在cookie中。
redis是分布式会话的不二之选;
在spring boot项目中,引入对redis的依赖,在pom.xml中添加依赖;
在application.properties中配置redis,配置springboot对redis的依赖,设置jedis连接池;
服务器内搭建redis并且配置ip地址;
token本质上相当于一个令牌的概念,登录操作时,服务端类似于下发sessionid植入到cookie中,下发一个令牌传给前端,前端可以将令牌存储起来,使得在下一次存储中对应的请求需要登录,则将对应的令牌和请求协同在一个固定的参数上,传给服务器,服务器再通过对应的token实现对应的会话凭证概念,完成对应的会话管理。