秒杀项目系列之三: 分布式扩展(nginx+两个应用服务器+数据库服务器)及jmeter压测和错误解决

  1. 单机服务器容量问题
  • 表象: 单机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压测时结果如下
    在这里插入图片描述
  1. 应用单机容量问题,水平扩展的解决方案
  • mysql数据库开放远端连接
  • 服务端水平对称部署(使用nginx反向代理实现负载均衡)
  • 验证访问
  • 最初系统架构图
    在这里插入图片描述
  • 使用nginx实现水平扩展、负载均衡系统架构图(需要四台服务器,一台nginx,一台mysql,两台java application)
    在这里插入图片描述
  1. 另外三台服务器配置(另外三台服务器使用了三台虚拟机,云服务器太贵了…)
  • 为四台服务器分别起名为秒杀应用服务器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     不能传输,没查出原因
      
  • 更改应用服务器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可以访问),方法如下:
      # 进入到数据库服务器
      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表内容:在这里插入图片描述
      执行创建root并赋予root权限后的user表内容:
      在这里插入图片描述
  • 防火墙开放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网络附属存储),也就是个人搭建的云存储…因为本地磁盘空间有限,不足以满足需求.

  2. 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",
        ...
    });
    
  1. 安装OpenResty
    基于nginx的OpenResty安装链接

  2. 将前端资源部署到nginx服务器

  • 将本机代码传输到nginx服务器命令
    # 在前端资源目录下执行
    sudo scp -r * root@192.168.145.7:/usr/local/openresty/nginx/html/
    
  • 查看nginx服务器下是否传输成功
    在这里插入图片描述
  • 本机查看nginx服务器前端界面
    在这里插入图片描述
  1. 配置前端资源路由
  • 修改本机客户端的/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)
    在这里插入图片描述

  1. 配置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反向代理访问应用服务器资源
      在这里插入图片描述
  1. 在上一步通过nginx反向代理访问应用服务器中碰到的大坑(nginx.conf文件)

    # 这原本写的$http_host:$proxy_port,但是报400错误,将http_host改成host后正确.原因没查到...
    proxy_set_header Host $host:$proxy_port;
    
  2. 开启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路径、状态码、发送信息字节数、响应时间等信息.
  1. 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
        ...
        
  • 测试

    • 线程数1000, ramp-up:10, 循环次数20
      • 分布式效果: 平均值: 425ms, 95值: 540ms, 吞吐量TPS: 1021/sec.
        在这里插入图片描述
      • 单机效果: 平均值: 695ms, 95值: 924ms, 吞吐量TPS: 783/sec
        在这里插入图片描述
    • 线程数2000, ramp-up:10, 循环次数20
      • 分布式效果: 平均值: 1194ms, 95值: 1327ms, 吞吐量TPS: 1073/sec.
        在这里插入图片描述
      • 单机效果: 平均值: 2038ms, 95值: 2588ms, 吞吐量TPS: 735/sec
        在这里插入图片描述
  • 总结

    • 虽然服务器的配置很低(1核2G),但是在经过解决压测时存在的问题后,四台分布式服务器(1nginx服务器、2应用服务器、1数据库服务器)的效果要比单机强很多.
    • 坑真多,这点东西搞了两天…
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值