Apache Bench(AB)的下载、安装与访问、测试

时间:2024-03-06 20:44:41

Apache Bench(AB)的下载

  首先我们得从Apache的官网上下载Apache Bench(AB)官网上的版本有32位与64位两个不同版本,这里我们可以根据我们的操作系统版本选择对应的软件安装包,由于我的操作系统位64位,所以说在这里我直接下载64位版本的Apache Bench(AB)。

 

 

 由于该网站为外网网站,所以说我们在用浏览器下载时不免会有些缓慢,如果遇到这种情况,请直接复制下载链接,然后使用迅雷进行下载,比如说我使用迅雷下载该软件,下载一个将近17M的安装包,所需的下载时间为3秒,之所以会这么快,是因为它有镜像加速功能。

 

 

 

Apache Bench(AB)的安装

解压到安装目录

  解压我们下载好的httpd-2.4.34-win64-VC15.zip到指定的安装目录,比如说我这里的安装目录为:

 

F:\Dev\httpd-2.4.34\Apache24

修改配置文件

  修改Apache Bench(AB)的配置文件,该配置文件在软件安装目录下的conf/httpd.conf,用编辑器打开该配置文件,将其中的服务器根目录修改成本地的安装目录,在这里,我的本地安装目录为:

Define SRVROOT "F:/Dev/httpd-2.4.34/Apache24"

修改Apache Bench(AB)的监听端口号,将其端口号由默认的80端口改成8088,如下:

Listen 8088

之所以这样改,主要是为了防止端口号冲突。比如说IIS一般喜欢占用80端口,而Tomcat一般喜欢占用8080端口,而上述两个端口号往往是web开发过程中经常会使用到的端口号,所以为了防止Apache Bench(AB)的端口号可能与上述两种端口号冲突,这里我将其端口号改成了8088。

修改servername

ServerName www.studylaravel.com:80

注册成系统服务

  以管理员身份运行cmd控制台,将控制台的光标切换成软件的安装目录,在该安装目录中,切换到bin目录,在其中运行如下指令:

httpd -k install

当我们看到下图中的successfully installed信息时,此时就说明我们的软件已经成功的注册成系统服务了。

 

注册后的系统服务如下所示:

 

 

启动并访问Apache Bench(AB)

启动Apache Bench(AB)

  我们可以采用图形界面的方式进行访问,在Apache Bench(AB)的安装目录下,找到bin/ApacheMonitor.exe,双击并启动模拟器,此时我们可以看到桌面的右下角中出现了该软件的小图标,我们可以通过该小图标以图形界面的形式启动该Apache Bench(AB)。

  我们通过点击小图标,然后依次点击Apache 2.4 -> Start来启动该软件。

访问Apache Bench(AB)

  在浏览器地址栏中输入http://localhost:8088/进行访问,当我们看到下面的信息时,此时就说明我们的软件已经能够正常使用了。

 

 参数很多,一般我们用 -c 和 -n 参数就可以了。例如:

ab -c 5000 -n 600 http://127.0.0.1/index.php

ApacheBench用法详解:

在Linux系统,一般安装好Apache后可以直接执行;
# ab -n 4000 -c 1000 http://www.ha97.com/

如果是Win系统下,打开cmd命令行窗口,cd到apache安装目录的bin目录下;

-n后面的4000代表总共发出4000个请求;-c后面的1000表示采用1000个并发(模拟1000个人同时访问),后面的网址表示测试的目标URL。

 

 结果分析:

This is ApacheBench, Version 2.3
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.80.157 (be patient)
Completed 400 requests
Completed 800 requests
Completed 1200 requests
Completed 1600 requests
Completed 2000 requests
Completed 2400 requests
Completed 2800 requests
Completed 3200 requests
Completed 3600 requests
Completed 4000 requests
Finished 4000 requests

Server Software: Apache/2.2.15
Server Hostname: 192.168.80.157
Server Port: 80

Document Path: /phpinfo.php
#测试的页面
Document Length: 50797 bytes
#页面大小

Concurrency Level: 1000
#测试的并发数
Time taken for tests: 11.846 seconds
#整个测试持续的时间
Complete requests: 4000
#完成的请求数量
Failed requests: 0
#失败的请求数量
Write errors: 0
Total transferred: 204586997 bytes
#整个过程中的网络传输量
HTML transferred: 203479961 bytes
#整个过程中的HTML内容传输量
Requests per second: 337.67 [#/sec] (mean)
#最重要的指标之一,相当于LR中的每秒事务数,后面括号中的mean表示这是一个平均值
Time per request: 2961.449 [ms] (mean)
#最重要的指标之二,相当于LR中的平均事务响应时间,后面括号中的mean表示这是一个平均值
Time per request: 2.961 [ms] (mean, across all concurrent requests)
#每个连接请求实际运行时间的平均值
Transfer rate: 16866.07 [Kbytes/sec] received
#平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 483 1773.5 11 9052
Processing: 2 556 1459.1 255 11763
Waiting: 1 515 1459.8 220 11756
Total: 139 1039 2296.6 275 11843
#网络上消耗的时间的分解,各项数据的具体算法还不是很清楚

Percentage of the requests served within a certain time (ms)
50% 275
66% 298
75% 328
80% 373
90% 3260
95% 9075
98% 9267
99% 11713
100% 11843 (longest request)
#整个场景中所有请求的响应情况。在场景中每个请求都有一个响应时间,其中50%的用户响应时间小于275毫秒,66%的用户响应时间小于298毫秒,最大的响应时间小于11843毫秒。对于并发请求,cpu实际上并不是同时处理的,而是按照每个请求获得的时间片逐个轮转处理的,所以基本上第一个Time per request时间约等于第二个Time per request时间乘以并发请求数。

 并发测试

准备两张数据表:

CREATE TABLE `concurrent` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `version` bigint(20) unsigned NOT NULL,
  `stock` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `concurrent_log` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2967 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

数据:

INSERT INTO `ceshi1123`.`concurrent` (`id`, `name`, `version`, `stock`) VALUES (\'1\', \'5f69af49eb1d6\', \'0\', \'50\');

目前库存为50个,下面进行代码编写:

    public function update_default_stock()
    {
        $name = uniqid();
        try{
//            DB::beginTransaction();
            $c = DB::table(\'concurrent\')->where(\'id\', 1)->first();
            if($c->stock < 1){
                throw new \Exception("error:stock");
            }
            $a = DB::table(\'concurrent\')->update([\'name\'=>$name, \'version\'=>DB::raw(\'version+1\'), \'stock\'=>DB::raw(\'stock-1\')]);
            DB::table(\'concurrent_log\')->insert([\'content\'=>$name.":stock:".$c->stock.":default_stock:".$a]);
//            DB::commit();
        }catch (\Throwable $e){
//            DB::rollBack();
            DB::table(\'concurrent_log\')->insert([\'content\'=>$e->getMessage()]);
        }
    }

执行测试命令,-n表示总共100次请求,-c表示每次100个并发(同时100个访问):

ab -n 100 -c 100 http://www.studylaravel.com/concurrent/stock

 

 

 执行完毕,但是发现库存已经超出范围,执行了52次更新:

 

 

 第二次测试,加上事务

代码修改:

public function update_default_stock()
    {
        $name = uniqid();
        try{
            DB::beginTransaction();
            $c = DB::table(\'concurrent\')->where(\'id\', 1)->first();
            if($c->stock < 1){
                throw new \Exception("error:stock");
            }
            $a = DB::table(\'concurrent\')->update([\'name\'=>$name, \'version\'=>DB::raw(\'version+1\'), \'stock\'=>DB::raw(\'stock-1\')]);
            DB::table(\'concurrent_log\')->insert([\'content\'=>$name.":stock:".$c->stock.":default_stock:".$a]);
            DB::commit();
        }catch (\Throwable $e){
            DB::rollBack();
            DB::table(\'concurrent_log\')->insert([\'content\'=>$e->getMessage()]);
        }
    }

还原测试前的数据:

 

 

 开始测试:

 

 

 结果为:

 

 

 还是没有解决卖超的问题。怎么办呢?

使用共享锁试试

修改代码:

public function update_sharedLock_stock()
    {
        $name = uniqid();
        try{
            DB::beginTransaction();
            $c = DB::table(\'concurrent\')->where(\'id\', 1)->sharedLock()->first();
            if($c->stock < 1){
                throw new \Exception("error:stock");
            }
            $a = DB::table(\'concurrent\')->update([\'name\'=>$name, \'version\'=>DB::raw(\'version+1\'), \'stock\'=>DB::raw(\'stock-1\')]);
            DB::table(\'concurrent_log\')->insert([\'content\'=>$name.":stock:".$c->stock.":default_stock:".$a]);
            DB::commit();
        }catch (\Throwable $e){
            DB::rollBack();
            DB::table(\'concurrent_log\')->insert([\'content\'=>$e->getMessage()]);
        }
    }

共享锁说明:https://www.cnblogs.com/jiangxiaobo/p/13705711.html,共享锁对得到锁的用户是运行修改、读取,但是未得到锁的用户只能读取,不能修改。

继续测试:

 

 

 成功部分,错误部分是因为死锁造成失败的:

 

 

 

SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction (SQL: update `concurrent` set `name` = 5f69b2d9c1d2b, `version` = version+1, `stock` = stock-1)

使用排它锁试试

修改代码:

public function update_lockForUpdate_stock()
    {
        $name = uniqid();
        try{
            DB::beginTransaction();
            $c = DB::table(\'concurrent\')->where(\'id\', 1)->lockForUpdate()->first();
            if($c->stock < 1){
                throw new \Exception("error:stock");
            }
            $a = DB::table(\'concurrent\')->update([\'name\'=>$name, \'version\'=>DB::raw(\'version+1\'), \'stock\'=>DB::raw(\'stock-1\')]);
            DB::table(\'concurrent_log\')->insert([\'content\'=>$name.":stock:".$c->stock.":default_stock:".$a]);
            DB::commit();
        }catch (\Throwable $e){
            DB::rollBack();
            DB::table(\'concurrent_log\')->insert([\'content\'=>$e->getMessage()]);
        }
    }

继续测试:

 

 

 发现排它锁是可以的,就是会阻塞,造成请求过慢,但是保证数据稳定

 

使用乐观锁试试

修改代码:

public function update_optimistic_lock_stock()
    {
        $name = uniqid();
        try{
            DB::beginTransaction();
            $c = DB::table(\'concurrent\')->where(\'id\', 1)->first();
            if($c->stock < 1){
                throw new \Exception("error:stock");
            }
            $a = DB::table(\'concurrent\')
                ->where(\'id\', 1)
                ->where(\'version\', $c->version)
                ->update([\'name\'=>$name, \'version\'=>DB::raw(\'version+1\'), \'stock\'=>DB::raw(\'stock-1\')]);
            if(empty($a)){
                throw new \Exception("error:nothing");
            }
            DB::table(\'concurrent_log\')->insert([\'content\'=>$name.":stock:".$c->stock]);
            DB::commit();
        }catch (\Throwable $e){
            DB::rollBack();
            DB::table(\'concurrent_log\')->insert([\'content\'=>$e->getMessage()]);
        }
        echo 1;
    }

继续测试:

 

 

 发现只有部分成功了!,看下日志:

 

 

 发现大部分请求什么都失败了,因为同时更新相同version只能有一个成功。这里为了避免这种情况,可以采用下面这种方式来处理:

public function update_optimistic_lock_stock2()
    {
        $name = uniqid();
        try{
            DB::beginTransaction();
            $c = DB::table(\'concurrent\')->where(\'id\', 1)->first();
            if($c->stock < 1){
                throw new \Exception("error:stock");
            }
            $a = DB::table(\'concurrent\')
                ->where(\'id\', 1)
                ->where(\'stock\', \'>\', 0)
                ->update([\'name\'=>$name, \'version\'=>DB::raw(\'version+1\'), \'stock\'=>DB::raw(\'stock-1\')]);
            if(empty($a)){
                throw new \Exception("error:nothing");
            }
            DB::table(\'concurrent_log\')->insert([\'content\'=>$name.":stock:".$c->stock]);
            DB::commit();
        }catch (\Throwable $e){
            DB::rollBack();
            DB::table(\'concurrent_log\')->insert([\'content\'=>$e->getMessage()]);
        }
        echo 1;
    }

继续测试: