项目中经常遇到的三高问题是指一个系统的高并发、高可用、高性能问题,这三个问题和其他例如安全问题,监控问题等,就是从多维度不同指标来衡量系统特性。例如:常用响应时间、QPS、吞吐量等指标来衡量系统并发性;常用年度运行天数占比来衡量可靠(可用)性,例如全年故障3.65天,则可靠性为(365-3.65)/365=99%;常用服务响应时间来衡量高性能。
对于单机应用来说,除了操作系统对进程的线程数量约束之外,主机自身的网络、IO、CPU、内存自身的物理限制等也会制约单机系统的性能。所以,采用集群模式,结合模块解耦,无状态服务和服务冗余等架构设计,可以跨系统跨主机甚至跨集群地将处理的负载进行横向扩展用以提高系统整体的处理性能,同时冗余设计提高了可靠性,集群模式同时也会带来跨系统跨主机造成的数据共享问题,所以又需要引进其他中间件,例如Redis,ZooKeeper等来增强系统的数据共享管理、可靠性管理等。
Web项目架构
在Web项目中,常常将单一功能的单机程序进行横向扩展来实现三高目标,一个方案是采用Nginx和应用服务的集群结合的架构。这里以一个简单的web项目为例,使用Nginx反向代理SpringBoot应用服务器,并采用Redis中间件来实现集群的session一致性共享。架构如下:
SpringBoot Web应用
从SpringBoot的spring-configuration-metadata.json可知,springboot内置的tomcat 最大线程数默认为200,即单机的最大线程数为200,所以单机场景下线程数会对QPS并发吞吐量有制约效应。
这里以一个单机Web应用为例,接口set接口用于登录系统,get接口用于获取登录信息,clear接口用于清理登录信息,如下:
同时,使用spring session + Redis的模式,来自动共享接口的session数据;
另外,在pom.xml文件中,使用fabric8/docker-maven-plugin插件来构建这个web应用的docker镜像,构建完毕后运行三个容器,各个容器端口不同,如下:
然后在三个浏览器进程中 进行伪登录
这样通过get接口访问获取当前登录"用户"的sessionID,可查看到不同进程的用户登录后,其会在Redis中生成对应的session值,其key值刚好等于sessionID,说明每个APP都能独立的完成用户session同步到Redis过程。
Nginx
nginx是一个高性能的HTTP和反向代理服务器,与正向代理(例如VPN应用)配置在客户端不同,反向代理的服务器配置是在服务端,它以一个虚拟IP来代理客户端请求并返回响应,稳定性相比Apache要好,内存消耗非常少。
nginx核心默认配置文件是 nginx.conf,nginx.conf文件也可以以include的形式引入其他配置文件。nginx配置结构包括:
- 全局配置:工作线程,使用用户,日志,进程pid等;
- events块配置:nginx与用户网络连接配置,开启序列化,多路复用等;
- http块配置:嵌套多个server,配置日志格式,其他模块引入等;
- server块配置:配置监听端口,请求转发拦截等;
其常用的配置如下:
# 运行的用户
user nginx;
# 工作线程
worker_processes auto;
# 如果配置多个,需要再加cpu配置
# 例如4个线程:worker_cpu_affinity 1000 0100 0010 0001;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
# 工作模式
events {
# 配置epoll io多路复用有助于提高性能;
use epoll;
# 单个工作线程最大并发连接数
worker_connections 1024;
}
http {
# 设定资源类型,png,css,js等;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 设定日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# 是否zero copy方式传输文件,一般都设置为on
sendfile on;
# 网络阻塞时自动请求
#tcp_nopush on;
# HTTP/1.0 每次请求都会建立连接,任务结束就中断;HTTP/1.1 请求连接成功后建立了SOCKET不管连接是否使用都保持连接,keepalive 设置了连接保活时长为65秒
keepalive_timeout 65;
#gzip on;
# 引入其他配置
include /etc/nginx/conf.d/*.conf;
}
在引入的其他配置文件夹/etc/nginx/conf.d中找到default.conf,结构如下:
server {
# 指定监听端口
listen 80;
listen [::]:80;
server_name localhost;
# 匹配路由,root表示默认的网站根目录位置,docker中配置在/usr/share/nginx/html,windows版本配置在安装包根目录的html目录,index表示依次尝试访问的主页
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# 404错误返回的页面
#error_page 404 /404.html;
# 500错误返回的页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# ~表示路由匹配正则表达式
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# 拒绝访问的路由
#location ~ /\.ht {
# deny all;
#}
}
这里,准备一台Nginx服务器,用来代理上述3台APP的访问服务。在http模块中,监听80端口,并将请求转发到端口为8081、8082、8083的三台主机上,默认upstream中的负载均衡为轮询,详细配置如下:
然后使用docker -v 数据卷的方式将配置同步到容器中:
然后通过页面访问直接nginx,效果如下:
可看到通过nginx访问后台服务,不同进程的用户session并未丢失,redis服务器也未创建新的session信息,这样就由nginx反向代理实现对用户屏蔽后台服务端口的功能。
运行测试
高性能特性可通过nginx的路由配置来实现动静分离和负载均衡,通过优化静态资源缓存,压缩,连接数,线程数等来优化性能。在集群模式下,将默认tomcat的200线程横向扩展到200*3=600,使得并发量增加,相应的吞吐性能也会提升,可借助jmeter开源工具对单机和nginx集群模式分别进行接口测试。
高并发高可靠性可通过nginx的负载均衡来实现,其中负载均衡模式有轮询,ip_hash,权重等多种,对于单个主机的宕机,nginx能自动切换其他可用的单机服务.为防止nginx单点问题,部署多节点nginx并为每个nginx节点配置keepalive服务,这样通过keepalive 的VIP访问即可增加集群的可用和可靠性。
高可用测试
现在后台运行了3台服务器,如果其中一台宕机,会影响用户使用吗,能满足高可用性吗?尝试让端口为8081的机器宕机:
然后通过Nginx来看看访问情况,重点关注返回值中localport的变化:
可看到不同进程的用户通过Nginx访问,在8081端口宕机的情况下,仍然可以正常访问,其用户的sessionid依然保存在redis服务器中。再次让端口号为8082的机器宕机,看看效果:
通过nginx访问的效果如下,重点关注返回值中localport的变化:
可看到在8081、8082号端口的主机宕机的情况下,依然能够正常访问。再次让端口为8083的机器宕机:
通过Nginx访问发现主机全部宕机的情况下,访问已不可用了。
这时再次重启所有的主机:
然后使用nginx来进行访问,发现服务已上线并可访问,其效果如下,重点关注不同进程返回值中localport的变化::
由上述测试可知,基于nginx反向代理的集群架构,可以实现系统的高可用,高可靠性。在这个项目里,服务集群化冗余,加上nginx负载均衡来实现高可用高可靠,另外在软件灰度发布时可以加以利用以实现部分系统下线也保证系统的总体平稳。Nginx作为反向代理服务器,还会涉及到动静分离、Keepalive配置、HTTPS安全、请求跨域、网络限流等问题,在大数据领域也会有结合Lua实现流式处理的需求,这些都需要根据业务需要和场景来综合判断取舍。