关于workerman:webman-框架简单体验

94次阅读

共计 6786 个字符,预计需要花费 17 分钟才能阅读完成。

webman 是一款基于 workerman 开发的 http 服务框架,用于开发 web 站点或者 http 接口。反对路由、中间件、主动注入、多利用、自定义过程、无需更改间接兼容现有 composer 我的项目组件等诸多个性。具备学习成本低、简略易用、超高性能、超高稳定性等特点。https://www.workerman.net/doc…

简略来说,webman 是基于 workerman 的一款常驻内存的 利用 服务框架,运行模式为多过程阻塞模式,IO 模型必定是多路复用,至于是select/poll 还是 epoll 应该同 workerman 的场景统一,看是否装置了 event 扩大了(倡议装置,高并发下 epoll 模型更具劣势)。

尽管不像以后许多基于 swoole 协程 或 相似 node/reactPHPeventLoop 的异步非阻塞模式的框架,但基于 epoll 模型时,开 cpuworker 单机 C10K 也没什太大鸭梨。

小课堂

单过程模式

一个服务过程,来一个申请就阻塞,解决期间回绝响应其余申请。
1、开始期待以后申请 网络 IO实现。
2、紧接着解决代码业务(期间可能也会随同着各种 网络 IO,你的业务网代码总不能只是 "hello world" 吧,数据库 IO文件 IO、调用其余微服务的 网络 IO,都会产生阻塞)。
3、发送响应结束。能够持续接管解决下一个申请。
毛病:无奈承载高并发,你将会收到各种 502 响应。

多线程 / 协程模式

一个主服务过程,来一个申请就创立一个线程去专用解决,线程专一解决负责的申请。相比单过程模式,能够承载较高的申请并发量,但创立和切换线程的开销也是很大的,还有死锁的问题(当初又有了 协程,用户态线程,更加轻量级,还能够)。

IO 多路复用模式

IO 多路复用模式下,worker 过程在接管一个申请后,如果该申请还未就绪(内核还未实现 socket 数据的读取及未 copy 至用户态),那么 worker 是能够持续去接管其余申请的,当某申请的 socket 数据读取实现后,worker 便开始执行业务解决(留神:此阶段 worker 是被业务解决独占的,期间无奈解决其余申请)。业务解决实现,worker 被开释,复原最后的状态流程。select/pollepoll 都是 IO 多路复用,不同之处在于 epoll 采纳更敌对的告诉机制,select/poll 要被动的忙轮训来监测是否有已就绪的申请socketepoll 则是期待内核的被动告诉。

EventLoop 模式

nodereactPHP 是比拟典型的代表。workerman 也有内置的 eventLoopFactory,借用 reactPHP 生态的异步客户端就能够实现高性能的 eventLoop 模式,性能优异,但不太实用简单的业务解决,异步格调的 callback hell 大家应该都有理解。事件队列保护申请的上下文,申请 IO 就绪时会事件告诉 worker 来持续上面的操作,如果产生了 IO 就入队事件队列,期待 IO 实现了再号召 worker,所以 worker 始终在执行流程管制的业务代码,一旦产生了 IO 阻塞,就会把申请上下文放入事件队列,去解决其余申请的事件。

网上比拟形象的例子,幼儿园老师分糖吃。比方咱们有 100 个地位,A 来了,老师说坐下,老师并不会盯着 A 去入座,这时候 A 还未坐好,不能给糖吃(内核还未实现申请 socket 的读取)。B、C 来了,老师说坐下、坐下。A 说坐好了要吃糖,老师走过来把糖给 A,A 开始吃糖(数据库 IO,网络 IO),老师并不会杵在那里看 A 吃糖(这里可能不太形象,你就想着吃糖要人喂,但不是老师做,是另外的 cpu 工夫片)。C 说坐好了要吃糖,老师把糖给 C。D 来了,老师说坐下。B 说坐好了要吃糖,老师把糖给 B。A 说吃完了,老师让 A 回去(响应申请),把糖纸回收(清理回收资源)。这样老师就能关照很多个孩子一起吃糖。

尽管没有协程加持,没有 eventLoop,但 多路 IO 复用 下的 epoll 模式仍然能让 webman 承载高并发申请(只有你业务代码不坨,申请的网络 IO 阻塞能够凭借 epoll 模型实现保护 c10k 个,谁筹备好了再去解决业务代码这种运行模式)。

压测

配置
i5-7360U CPU @ 2.30GHz 2 Core 4 Thread
8G RAM

开了 4 个 worker 过程

Workerman[start.php] start in DEBUG mode
------------------------------------------- WORKERMAN --------------------------------------------
Workerman version:4.0.18          PHP version:7.4.2
-------------------------------------------- WORKERS ---------------------------------------------
proto   user            worker          listen                      processes    status
tcp     sqrtcat     webman          http://0.0.0.0:8787         4             [OK]
tcp     sqrtcat     monitor         none                        1             [OK]
tcp     sqrtcat     websocket       websocket://0.0.0.0:8888    10            [OK]
--------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.

当初查看 mysqlprocesslist 并不会有 webmanworker 建设的链接,因为链接会在 worker 首次对数据库拜访时建设,后续就放弃长链接啦。

mysql> show processlist;
+----+-----------------+-----------+------+---------+--------+------------------------+------------------+
| Id | User            | Host      | db   | Command | Time   | State                  | Info             |
+----+-----------------+-----------+------+---------+--------+------------------------+------------------+
|  4 | event_scheduler | localhost | NULL | Daemon  | 115720 | Waiting on empty queue | NULL             |
| 13 | root            | localhost | NULL | Query   |      0 | starting               | show processlist |
+----+-----------------+-----------+------+---------+--------+------------------------+------------------+
2 rows in set (0.01 sec)

为什么 webman 没有数据库连接池呢?
因为 webmanworker 工作模式为 IO 多路复用 ,每个 worker 都能够在同申请建设链接后, 申请传输数据期间 能够 不阻塞 的去解决其余申请,待以后申请的数据IO 就绪后,worker 会一口气执行 业务代码 直至 实现 ,执行期间 worker 是被 齐全占用 的,与 worker 绑定的 dbConnect 也是被以后 业务上下文 持有的。所以执行 业务代码 期间 worker 并不能 转出 再去连接池取一个 新的 dbConnect 去执行别的申请的业务(即协程或者异步的模式,能够在业务阻塞时转出,执行其余申请的业务代码),连接池也就没有存在的意义了。

我先小跑一下把 db 链接 跑进去更直观大家了解,能够看到每个 worker 建设了一个链接(在某些方面来说这也是个简略的连接池,避免数据库被申请打崩掉是齐全可控的了)。

mysql> show processlist;
+----+-----------------+-----------------+--------+---------+--------+------------------------+------------------+
| Id | User            | Host            | db     | Command | Time   | State                  | Info             |
+----+-----------------+-----------------+--------+---------+--------+------------------------+------------------+
|  4 | event_scheduler | localhost       | NULL   | Daemon  | 116593 | Waiting on empty queue | NULL             |
| 13 | root            | localhost       | NULL   | Query   |      0 | starting               | show processlist |
| 14 | root            | localhost:50426 | webman | Sleep   |      4 |                        | NULL             |
| 15 | root            | localhost:50436 | webman | Sleep   |      4 |                        | NULL             |
| 16 | root            | localhost:50438 | webman | Sleep   |      4 |                        | NULL             |
| 17 | root            | localhost:50437 | webman | Sleep   |      4 |                        | NULL             |
+----+-----------------+-----------------+--------+---------+--------+------------------------+------------------+
6 rows in set (0.00 sec)
压测代码

控制器 app/controller/Index.php

/**
 * 数据 IO 业务模仿演示
 * @return Response
 */
public function db()
{$nameList  = ['james', 'lucy', 'jack', 'lilei', 'lily'];
    $hobbyList = ['football', 'basketball', 'swimming'];
    
    $name  = $nameList[array_rand($nameList)];
    $hobby = $hobbyList[array_rand($hobbyList)];

    if (mt_rand(0, 5) >= 2) {// 0- 1 读 2- 5 写
        $insertId = Db::table('test')->insertGetId([
            'name'  => $name,
            'age'   => rand(20, 100),
            'sex'   => ['m', 'f'][array_rand(['m', 'f'])],
            'hobby' => $hobby,
        ]);
        $data =  ['id' => $insertId];
    } else {$data = Db::table('test')->where('hobby', $hobby)->first();}

    return json(['msg' => 'success', 'data' => $data]);
}
压测示例
5w 申请 200 并发

ab -c 200 -n 50000 -k http://0.0.0.0:8787/index/db

Server Software:        workerman
Server Hostname:        0.0.0.0
Server Port:            8787

Document Path:          /index/db
Document Length:        87 bytes

Concurrency Level:      200
Time taken for tests:   15.025 seconds
Complete requests:      50000
Failed requests:        0
Keep-Alive requests:    50000
Total transferred:      8413864 bytes
HTML transferred:       2713864 bytes
Requests per second:    3327.84 [#/sec] (mean)
Time per request:       60.099 [ms] (mean)
Time per request:       0.300 [ms] (mean, across all concurrent requests)
Transfer rate:          546.88 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0      13
Processing:     2   60   9.9     58     183
Waiting:        2   60   9.9     58     183
Total:          2   60   9.8     58     183

Percentage of the requests served within a certain time (ms)
  50%     58
  66%     61
  75%     64
  80%     66
  90%     70
  95%     73
  98%     84
  99%    102
 100%    183 (longest request)
5w 申请 500 并发

ab -c 500 -n 50000 -k http://0.0.0.0:8787/index/db

Server Software:        workerman
Server Hostname:        0.0.0.0
Server Port:            8787

Document Path:          /index/db
Document Length:        86 bytes

Concurrency Level:      500
Time taken for tests:   14.833 seconds
Complete requests:      50000
Failed requests:        0
Keep-Alive requests:    50000
Total transferred:      8404497 bytes
HTML transferred:       2704497 bytes
Requests per second:    3370.91 [#/sec] (mean)
Time per request:       148.328 [ms] (mean)
Time per request:       0.297 [ms] (mean, across all concurrent requests)
Transfer rate:          553.34 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   2.3      0      35
Processing:     5  147  16.7    146     311
Waiting:        1  147  16.7    146     311
Total:          6  147  15.9    146     311

Percentage of the requests served within a certain time (ms)
  50%    146
  66%    152
  75%    155
  80%    157
  90%    162
  95%    169
  98%    179
  99%    206
 100%    311 (longest request)
5w 申请 798 并发

ab -c 798 -n 50000 -k http://0.0.0.0:8787/index/db

Server Software:        workerman
Server Hostname:        0.0.0.0
Server Port:            8787

Document Path:          /index/db
Document Length:        38 bytes

Concurrency Level:      798
Time taken for tests:   14.412 seconds
Complete requests:      50000
Failed requests:        0
Keep-Alive requests:    50000
Total transferred:      8404559 bytes
HTML transferred:       2704559 bytes
Requests per second:    3469.37 [#/sec] (mean)
Time per request:       230.013 [ms] (mean)
Time per request:       0.288 [ms] (mean, across all concurrent requests)
Transfer rate:          569.50 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   4.9      0      57
Processing:     9  227  32.6    232     365
Waiting:        2  227  32.6    232     365
Total:         10  227  31.3    232     368

Percentage of the requests served within a certain time (ms)
  50%    232
  66%    244
  75%    249
  80%    251
  90%    258
  95%    265
  98%    280
  99%    300
 100%    368 (longest request)

能够看到 qps 稳固在 3500 左右,2Core 下的日常 db 操作这个 qps 我感觉很棒了。

正文完
 0