购买两台按量付费的云主机,第一台运行被压测的服务,另一台做压测机,配置如下:

  • 实例:通用型 g5 / ecs.g5.xlarge(4核CPU 16GiB)
  • 系统盘 : SSD云盘 20GiB
  • 镜像 : Ubuntu 16.04 64位

开通了3个小时,成本10块钱左右。

内核参数调优

#该参数设置系统的TIME_WAIT的数量,如果超过默认值则会被立即清除
net.ipv4.tcp_max_tw_buckets = 20000
#定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数
net.core.somaxconn = 65535
#对于还未获得对方确认的连接请求,可保存在队列中的最大数目
net.ipv4.tcp_max_syn_backlog = 262144
#在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
net.core.netdev_max_backlog = 30000
#能够更快地回收TIME-WAIT套接字。此选项会导致处于NAT网络的客户端超时,建议为0
net.ipv4.tcp_tw_recycle = 0
#系统所有进程一共可以打开的文件数量
fs.file-max = 6815744
#设置单进程允许打开文件数
ulimit -HSn 102400

程序调优

  • nginx 设置为固定4个进程(和cpu核心数相同),使用epoll,单进程10240个连接
  • php-fpm设置为固定512个进程,开启opcache,关闭opcache的自动刷新,和nginx通讯使用socket文件
  • laravel5.6框架 关闭session等中间件,关闭debug模式,运行artisan命令缓存调优
  • thinkphp5.1框架 关闭session 关闭debug

压测对象

php无框架

<?php
usleep(50000); // 模拟业务逻辑操作数据耗时等 50ms
echo str_repeat('s', 4000); // 模拟接口返回4kb数据

php laravel框架

<?php
Route::get('/', function () {
    usleep(50000);
    return str_repeat('s', 4000);
});

php thinkphp框架

<?php
namespace app\index\controller;
class Index
{
    public function index()
    {
        usleep(50000);
        return str_repeat('s', 4000);
    }
}

golang gin框架

package main
import (
    "github.com/gin-gonic/gin"
    "time"
    "strings"
)
func main()  {
    gin.SetMode(gin.ReleaseMode)
    router := gin.New()
    router.GET("/", func(c *gin.Context) {
        time.Sleep(time.Millisecond * 50)
        c.String(200, strings.Repeat("s", 4000))
    })
    router.Run(":8888")
}

压测方法

两台机器在同一内网内,直接执行 ab -c100 -n100000 http://hostname/

压测结果

gbench

分析

被压测的机器是4核心cpu,也就是说系统负载到4的时候,我们4个核心都跑到了100%,其他任务就要等待排队了。我们预设超过5倍cpu核心数(20)的负载状态是危险状态。负载到>=4的时候,这台机器每秒处理请求数基本已经确定了,因为cpu已经跑满了。从系统负载来看,php7无框架的情况,支持高并发还是可以的。laravel是很糟糕的,在这台机器上并发100就会持续高负载。以此类推thinkphp在这台机器上大概是400并发,就会持续高负载。golang在1500并发的时候负载才到4,说明golang在这台机器才刚到每秒处理请求数的极限,只有加核心才能每秒处理更多请求了。