- 单机服务器容量问题
- 表象: 单机cpu使用率增高,memory占用增加,网络带宽使用增加
- cpu us: 用户空间的cpu使用情况(用户层代码)
- cpu sy: 内核空间的cpu使用情况(系统调用)
- load average: 1,5,15分钟load平均值,跟着核数系数走,0表示通畅,1表示打满,1+表示等待堵塞(可以是介于0到1之间的小数)
- memory: free表示空闲内存,used表示使用内存
- top -H命令查看,使用jmeter压测时结果如下
- 应用单机容量问题,水平扩展的解决方案
- mysql数据库开放远端连接
- 服务端水平对称部署(使用nginx反向代理实现负载均衡)
- 验证访问
- 最初系统架构图
- 使用nginx实现水平扩展、负载均衡系统架构图(需要四台服务器,一台nginx,一台mysql,两台java application)
- 另外三台服务器配置(另外三台服务器使用了三台虚拟机,云服务器太贵了…)
-
为四台服务器分别起名为秒杀应用服务器1、秒杀应用服务器2、nginx反向代理服务器、秒杀数据库服务器
-
将原阿里云服务器上的www文件夹(包含jar包、配置文件、启动脚本文件等)拷贝到秒杀应用服务器1、2上的/var文件夹中.
- 为应用服务器的root设置密码方便传输(在创建虚拟机的时候没有设置,另外如果不使用root用户,需要修改var文件夹的权限,其他用户没有权限)
sudo passwd root # 输入目前使用用户的密码 # 输入两遍root用户的密码即可成功
- 修改传输的www文件夹权限
chmod 777 /var/www
- 在应用程序服务器中下载阿里云服务器的文件
# 进入到应用服务器1/2中 sudo scp -r root@101.37.171.33:/var/www /var # 输入应用服务器root密码和远程阿里云服务器密码即可 # 另外一种方法是从阿里云服务器传输文件到应用程序服务器,但是一直不动,没能成功 # scp -r /var/www root@192.168.145.5:/var 不能传输,没查出原因
- 为应用服务器的root设置密码方便传输(在创建虚拟机的时候没有设置,另外如果不使用root用户,需要修改var文件夹的权限,其他用户没有权限)
-
更改应用服务器1、2中/var/miaosha/application.yml配置文件的数据库url
# 101.37.171.33为秒杀数据库服务器的ip地址 url: jdbc:mysql://101.37.171.33:3306/miaosha?characterEncoding=utf8&useSSL=false
-
应用服务器连接数据库服务器
- 在应用服务器上测试下mysql数据库能不能通
# 如果telnet没有安装通过以下命令安装 [root@localhost miaosha]# yum install telnet [root@localhost miaosha]# telnet 服务器ip:3306 Trying 101.37.171.33... Connected to 101.37.171.33. Escape character is '^]'. EHost '58.212.52.59' is not allowed to connect to this MySQL serverConnection closed by foreign host.
- 可以看到被mysql拒绝连接(原因是mysql为了安全性,只允许在它的白名单中的host可以访问),方法如下:
执行创建root并赋予root权限前的user表内容:# 进入到数据库服务器 sudo ssh root@101.37.171.33 # 进入到mysql数据库 mysql -uroot -p123456 # 创建root用户,密码为123456,host为%表示所有host,意思是root用户可以从任何主机访问该mysql服务器. mysql> create user 'root'@'%' identified by '123456'; # 授予所有数据库的所有表的所有权限给root. *.*中的第一个*表示所有数据库,第二个*表示所有表 mysql> grant all privileges on *.* to 'root'@'%'; # 刷新权限 flush privileges; # 注:旧版的mysql创建和授权可以同时执行,语句如下: grant all privileges on *.* to 'root'@'%' identified by '123456';
执行创建root并赋予root权限后的user表内容:
- 在应用服务器上测试下mysql数据库能不能通
-
防火墙开放8090端口,否则客户端不能访问
# 两个应用服务器都要进行如下操作 firewall-cmd --add-port=8090/tcp --permanent # 重新加载 firewall-cmd --relaod # 查看 firewall-cmd --list-all
-
启动mysql及应用服务器jar
# 在数据库服务器上 systemctl start mysqld # 如果mysql已经启动,重启的话可以先通过ps -ef|grep mysqld查询出pid,使用kill pid命令关掉mysql进程,再执行上述命令 # 在应用服务器1上 cd /var/www/miaosha ./deploy.sh & # 应用服务器1同上 # 可以在同目录的nohup.out文件中查看是否正确启动
-
测试访问应用服务器结果
- 应用服务器1访问结果
- 应用服务器2访问结果
- 应用服务器1访问结果
-
使用nginx进行反向代理的系统架构图
注: 在实际应用中会使用NAS.(Network Attached Storage网络附属存储),也就是个人搭建的云存储…因为本地磁盘空间有限,不足以满足需求. -
host:port抽取到js文件中
-
目的: 方便在调试和服务器提供服务之间的切换.只需要修改一个文件即可.
-
创建gethost.js
var g_host="miaoshaserver:8090";
-
在所有使用到host:port的页面中导入该js文件
<head> <script src="./gethost.js" type="text/javascript"></script> </head>
-
g_host的使用
$.ajax({ ... url:"http://" + g_host + "/order/createorder", ... });
-
安装OpenResty
基于nginx的OpenResty安装链接 -
将前端资源部署到nginx服务器
- 将本机代码传输到nginx服务器命令
# 在前端资源目录下执行 sudo scp -r * root@192.168.145.7:/usr/local/openresty/nginx/html/
- 查看nginx服务器下是否传输成功
- 本机查看nginx服务器前端界面
- 配置前端资源路由
-
修改本机客户端的/etc/hosts文件,配置域名和nginx服务器映射
192.168.145.7 miaoshaserver
-
通过域名访问前端页面效果
-
修改nginx服务器上的nginx.conf文件,配置前端资源路由
-
在nginx服务器的/usr/local/openresty/nginx/html中新建resources,并将原本html目录下的所有文件放入resources中.
-
ngxin无缝重启(用户的链接不断,但是进程号pid发生改变)
./nginx -s reload
-
本机查看nginx服务器前端界面(可以看到url路径中多了resources)
- 配置nginx反向代理实现负载均衡,访问应用服务器
-
配置ngxin.conf (这把端口换成8090了,其实用什么都可以)
-
开放nginx服务器8090端口,以用于客户端请求服务
[root@localhost logs]# firewall-cmd --add-port=8090/tcp --permanent success [root@localhost logs]# firewall-cmd --reload success [root@localhost logs]# firewall-cmd --list-all
-
重启nginx
# 如果没有启动nginx,可以通过以下命令通过配置文件启动 ./nginx -c /usr/local/openresty/nginx/conf/nginx.conf # 如果启动了nginx,可以通过以下命令重启 ./nginx -s reload
-
查看效果
- 访问静态资源
- 通过nginx反向代理访问应用服务器资源
- 访问静态资源
-
在上一步通过nginx反向代理访问应用服务器中碰到的大坑(nginx.conf文件)
# 这原本写的$http_host:$proxy_port,但是报400错误,将http_host改成host后正确.原因没查到... proxy_set_header Host $host:$proxy_port;
-
开启tomcat access log(tomcat访问日志)
-
在两个应用服务器的miaosha目录下创建tomcat文件夹
[root@localhost miaosha]# mkdir tomcat [root@localhost miaosha]# chmod -R 777 tomcat/
-
修改application.yml配置文件,添加如下配置
server: tomcat: accesslog.enabled: true accesslog.directory: /var/www/miaosha/tomcat # %h:remote_host,即客户端请求host # %l:记录浏览者进行身份验证时提供的名字 # %u:remote_user # %t:time,处理时长 # "%r":输出http请求的第一行:请求方法和请求url # %s:http返回状态码 # %b:发送信息的字节数,不包括http头,如果字节数为0的话,显示为- # %D:请求消耗的时间,以毫秒记 accesslog.pattern: %h %l %u %t "%r" %s %b %D
改动后运行jar包时会报如下错误:
Caused by: org.yaml.snakeyaml.scanner.ScannerException: while scanning for the next token found character '%' that cannot start any token. (Do not use % for indentation) in 'reader', line 17, column 24: accesslog.pattern: %h %l %u %t "%r" %s %b %D
可添加下面依赖,再重新部署
<dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>版本号</version> </dependency>
为了避免重新部署,采用application.properties配置文件替换application.yml文件(坑: 注释不能写在后面,要另起一行,否则报错),内容如下:
server.port=8090 # 等待队列长度 server.tomcat.accept-count=1000 # 1核2G的最大线程数经验值为200,4核8G经验值为800,如果线程数太多,那么线程切换占用太多时间,反而是个拖累 server.tomcat.max-threads=200 # 为了解决突发容量问题,先开100个线程先用着 server.tomcat.min-spare-threads=100 server.tomcat.accesslog.enabled=true server.tomcat.accesslog.directory=/var/www/miaosha/tomcat # %h:remote_host,即客户端请求host # %l:记录浏览者进行身份验证时提供的名字 # %u:remote_user # %t:time,处理时长 # "%r":输出http请求的第一行:请求方法和请求url # %s:http返回状态码 # %b:发送信息的字节数,不包括http头,如果字节数为0的话,显示为- # %D:请求消耗的时间,以毫秒记 server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D spring.datasource.url=jdbc:mysql://101.37.171.33:3306/miaosha?characterEncoding=utf8&useSSL=false
-
运行jar包
./deploy.sh & # 如果已经在运行的话通过ps -ef|grep java查看pid,使用kill杀死,再启动
-
查看效果
- 在tomcat文件夹下自动生成访问日志文件: access_log.2021-01-17.log
- 应用服务器1的access_log.2021-01-17.log内容如下:
- 应用服务器2的access_log.2021-01-17.log内容如下:
- 可以看出客户端一共发了10次请求,由于nginx采用负载均衡的方法,所以每个应用服务器获取5个请求.当然也可以看出访问ip、时间、url路径、状态码、发送信息字节数、响应时间等信息.
- jemter进行压测
-
遇到的坑及解决办法
- 坑1: 使用分布式远比单机效果差,通过查询日志在nginx服务器的error.log文件中碰到如下错误:
accept4() failed (24: Too many open files)
- 原因分析
在linux系统中一切皆文件,所以并发的连接数也是文件,由于并发数太多,导致报这个错误. - 解决办法
# 查询用户可以同时开启的文件数,H表示设定资源的硬性限制,S表示资源的弹性限制 ulimit -Hn 1024(默认值) ulimit -Sn 4096(好像是这个值) # 查询系统可以同时打开文件的上限 sysctl -n -e fs.file-max # 在/etc/security/limits.conf中设置一个小于上限的值(将下面配置放到文件最后即可, *也是必须的) * soft nofile 65535 * hard nofile 65535 # 退出系统重进,通过ulimit -Hn和ulimit -Hn查看到同时打开文件数已经更新
- 原因分析
- 坑2: 使用jmeter进行压测,当并发数达到400-500时便出现以下报错
Cause: java.net.SocketException: Connection reset
-
如何查看jmeter日志(打开jmeter右上角的黄色感叹号)
-
原因分析
服务器连接超过最大并发数而重置,导致客户端连接超时.也就是说服务器连接关闭了,客户端还在读,导致错误.更加深层的原因是nginx和客户端默认长连接,但是nginx和服务器端默认短链接(因为nginx和应用服务器默认采用http1.0协议). -
确定nginx和服务器短为短连接的方法:使用jemter进行测试,同时在nginx服务器上执行以下命令
[zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 0 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 0 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 35 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 32 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 32 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 40 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 29 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 28 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 22 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 25 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 17 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l 5 [zlj@localhost nginx]$ netstat -an|grep 192.168.145.5|wc -l
可以看到nginx和应用服务器192.168.145.5之间的连接数一直在变.所以可以确定是短连接.
-
解决办法
修改nginx和服务器的连接为长连接(修改nginx和客户端的连接为短连接也可以解决这个错误,但这就不符合秒杀的环境了).# nginx.conf文件,添加如下参数 events { # 每个工作线程可以打开的并发连接数,默认为1024. # 当为默认值1024时,nginx的error.log报错1024 worker_connections are not enough worker_connections 10000; } upstream backend_server{ # 设置到upstream服务器的空闲keepalive连接的最大数量为30 keepalive 30; } location / { # 使用http1.1协议 proxy_http_version 1.1; # 将连接置为空,则默认使用keepalive proxy_set_header Connection ""; }
修改完后,保存退出,并重启nginx
可以通过下面命令看与服务器建立连接的详细信息,netstat -an | grep 192.168.145.5 | grep ESTABLISH # 通过结果看出pid不会发生改变,所以是长连接,如果是短链接,pid会一直变化. tcp 0 0 192.168.145.7:54704 192.168.145.5:8090 ESTABLISHED ...
-
- 坑1: 使用分布式远比单机效果差,通过查询日志在nginx服务器的error.log文件中碰到如下错误:
-
测试
- 线程数1000, ramp-up:10, 循环次数20
- 分布式效果: 平均值: 425ms, 95值: 540ms, 吞吐量TPS: 1021/sec.
- 单机效果: 平均值: 695ms, 95值: 924ms, 吞吐量TPS: 783/sec
- 分布式效果: 平均值: 425ms, 95值: 540ms, 吞吐量TPS: 1021/sec.
- 线程数2000, ramp-up:10, 循环次数20
- 分布式效果: 平均值: 1194ms, 95值: 1327ms, 吞吐量TPS: 1073/sec.
- 单机效果: 平均值: 2038ms, 95值: 2588ms, 吞吐量TPS: 735/sec
- 分布式效果: 平均值: 1194ms, 95值: 1327ms, 吞吐量TPS: 1073/sec.
- 线程数1000, ramp-up:10, 循环次数20
-
总结
- 虽然服务器的配置很低(1核2G),但是在经过解决压测时存在的问题后,四台分布式服务器(1nginx服务器、2应用服务器、1数据库服务器)的效果要比单机强很多.
- 坑真多,这点东西搞了两天…