关于一个http请求和数据库IO代码顺序导致api接口qps性能差异的思考

3 篇文章 0 订阅

引出问题如下:

   当一个基于http协议的papi接口中,有发起对第三方http接口的调用,且有多次数据库IO时,http请求代码放最前面和最后面性能有差异吗?

   前提如下:

          # 数据库有连接池,数量假设100, ab压测并发的客户端数为2000,即(ab -c2000 )

          # 使用Swoole协程http server。

 

php代码如下:

<?php


<?php
/**
 * Created by PhpStorm.
 * User: randy
 * Date: 2021/2/22
 */

$http = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_PROCESS);

\co::set(['hook_flags' => SWOOLE_HOOK_ALL]);

$http->on('start', function ($server) {
    echo "Swoole http server is started at http://127.0.0.1:9501\n";
});

$http->on('workerStart', function ($server) {
    //todo init redis conn
    class CachePool
    {
        /**
         * @var
         */
        private static $redisCli;

        const POOL_SIZE = 32;

        /**
         * @var \Swoole\Coroutine\Channel
         */
        private static $pool;


        public static function initPool()
        {
            self::$pool = new \Swoole\Coroutine\Channel(self::POOL_SIZE);
            for ($i = 0; $i < self::POOL_SIZE; $i++) {
                $conn = new Redis();
                $conn->connect("127.0.0.1", 6379);
                self::$pool->push($conn);
            }
        }

        /**
         * @return Redis
         */
        public static function getConn()
        {
            return self::$pool->pop();
        }

        public static function recycle($conn)
        {
            return self::$pool->push($conn);
        }

    }

  
    CachePool::initPool();

});

$http->on('request', function ($request, $response) use ($http) {

    //position 1
    $httpCli = new Swoole\Coroutine\Http\Client("127.0.0.1", 9090);
    $httpCli->get("/");
    $body = $httpCli->body;

    $redis = CachePool::getConn();

    if (empty($redis)) {
        var_dump("error");
        return;
    }

    defer(function () use ($redis) {
        CachePool::recycle($redis);
    });

    for ( $i=0; $i<8; $i++) {
      $redis->get("test_key1");
    } 

    //position 2

    $response->end("OK");
});


$http->start();

 该思考题的意思就是 发起http请求的代码出于 postion1  和处于 positon2时,是否有性能上的差距?

 

压测命令如下: ab -n20000 -c2000 -k http://127.0.0.1:9501/

结果:http请求放最前面比放最后面平均qps 高出3倍左右,分别压测6轮,得出的平均值 

 

为什么呢?

给出解答:(协程下面用coroutine代替,输入法不好打)

1、http请求放前面:当ab并发上来2000个客户端时,swoole会在瞬间创建2000个coroutine,且这2000个都在交替的执行,当执行到有数据库IO时,开始争夺连接池的连接,这时候因为连接池有限,实际上只有100个coroutine在并发的执行。当100个coroutine在并发数据库IO时,1900个http请求可能都已经返回结果了,所以后续的coroutine基本上省去了发起http请求的时间,直接从$redis = CachePool::getConn();开始执行代码。

2、http请求放后面:2000个并发一上来,前100个从数据库连接池拿到连接的coroutine,交替执行,剩下1900个在等待有空闲连接出现,这步就是在浪费cpu的时间了。当数据库io执行完成,100个coroutine交替发起http请求。这里有个关键问题时,协程没有执行完成,没有defer回收之前,连接是不会放进连接池的。

总结:

根本原因就是:连接池有数量限制压住了协程的并发,因为连接数用光之后,剩下的coroutine只能干等,而http请求你并发来多少,对应就有多少个coroutine交替并发的执行。1比2快在很多coroutine已经提现把http请求结果拿回来了,只需要执行数据库IO了。

 

上面的问题可以抽象成一个模型:

 2000辆车 同时从地点A 到 B,距离1公里,假设所有车都是匀速20m/s,不考虑加塞什么的特殊情况

场景1:  假设A到B的公路设计为: 前500米2000车道宽,后500米100车道宽

场景2:  假设A到B的公路设计为: 前500米100车道宽,后500米2000车道宽

哪种场景2000辆车先到地点B?

从A到B耗时 = 1000/20 = 50秒

场景1 其实就是 先2000并发执行一会,然后100并发执行一会,  前500米耗时=25秒 , 后500米耗时= (2000/100) *25 = 500  总耗时= 500 + 25 = 525秒

场景2 其实就是100一组,并发执行,总耗时 = (2000/100 ) * 50 = 1000 秒

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个简单的输出 MySQL QPS性能指标的 shell 脚本: ```bash #!/bin/bash # MySQL 登录信息 USER="username" PASSWORD="password" HOST="localhost" PORT="3306" DB="database_name" # 获取当前时间戳 TIMESTAMP=$(date +%s) # 执行 show global status 命令并保存结果到文件 mysql -u $USER -p$PASSWORD -h $HOST -P $PORT -e "show global status" > status_${TIMESTAMP}.txt # 从结果文件中解析出 QPS 和其他性能指标 COM_COMMIT=$(cat status_${TIMESTAMP}.txt | grep -w "Com_commit" | awk '{print $2}') COM_ROLLBACK=$(cat status_${TIMESTAMP}.txt | grep -w "Com_rollback" | awk '{print $2}') INNODB_ROW_LOCK_TIME_AVG=$(cat status_${TIMESTAMP}.txt | grep -w "Innodb_row_lock_time_avg" | awk '{print $2}') INNODB_ROW_LOCK_TIME_MAX=$(cat status_${TIMESTAMP}.txt | grep -w "Innodb_row_lock_time_max" | awk '{print $2}') INNODB_ROW_LOCK_TIME_MIN=$(cat status_${TIMESTAMP}.txt | grep -w "Innodb_row_lock_time_min" | awk '{print $2}') INNODB_ROW_LOCK_TIME=$(cat status_${TIMESTAMP}.txt | grep -w "Innodb_row_lock_time" | awk '{print $2}') INNODB_ROWS_READ=$(cat status_${TIMESTAMP}.txt | grep -w "Innodb_rows_read" | awk '{print $2}') INNODB_ROWS_UPDATED=$(cat status_${TIMESTAMP}.txt | grep -w "Innodb_rows_updated" | awk '{print $2}') UPTIME=$(cat status_${TIMESTAMP}.txt | grep -w "Uptime" | awk '{print $2}') # 计算 QPS TOTAL_QUERIES=$((COM_COMMIT + COM_ROLLBACK)) QPS=$(echo "scale=2; $TOTAL_QUERIES / $UPTIME" | bc) # 输出结果 echo "QPS: $QPS" echo "InnoDB Row Lock Time: avg=$INNODB_ROW_LOCK_TIME_AVG, max=$INNODB_ROW_LOCK_TIME_MAX, min=$INNODB_ROW_LOCK_TIME_MIN, total=$INNODB_ROW_LOCK_TIME" echo "InnoDB Rows Read: $INNODB_ROWS_READ" echo "InnoDB Rows Updated: $INNODB_ROWS_UPDATED" # 删除结果文件 rm status_${TIMESTAMP}.txt ``` 你可以将上述代码保存为一个 `mysql_perf.sh` 文件,然后在终端运行 `bash mysql_perf.sh` 来执行脚本。请注意,你需要将脚本中的 MySQL 登录信息和数据库名称替换为你自己的信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值