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 Thread8G 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    statustcp     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:        workermanServer Hostname:        0.0.0.0Server Port:            8787Document Path:          /index/dbDocument Length:        87 bytesConcurrency Level:      200Time taken for tests:   15.025 secondsComplete requests:      50000Failed requests:        0Keep-Alive requests:    50000Total transferred:      8413864 bytesHTML transferred:       2713864 bytesRequests 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] receivedConnection Times (ms)              min  mean[+/-sd] median   maxConnect:        0    0   0.5      0      13Processing:     2   60   9.9     58     183Waiting:        2   60   9.9     58     183Total:          2   60   9.8     58     183Percentage 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:        workermanServer Hostname:        0.0.0.0Server Port:            8787Document Path:          /index/dbDocument Length:        86 bytesConcurrency Level:      500Time taken for tests:   14.833 secondsComplete requests:      50000Failed requests:        0Keep-Alive requests:    50000Total transferred:      8404497 bytesHTML transferred:       2704497 bytesRequests 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] receivedConnection Times (ms)              min  mean[+/-sd] median   maxConnect:        0    0   2.3      0      35Processing:     5  147  16.7    146     311Waiting:        1  147  16.7    146     311Total:          6  147  15.9    146     311Percentage 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:        workermanServer Hostname:        0.0.0.0Server Port:            8787Document Path:          /index/dbDocument Length:        38 bytesConcurrency Level:      798Time taken for tests:   14.412 secondsComplete requests:      50000Failed requests:        0Keep-Alive requests:    50000Total transferred:      8404559 bytesHTML transferred:       2704559 bytesRequests 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] receivedConnection Times (ms)              min  mean[+/-sd] median   maxConnect:        0    1   4.9      0      57Processing:     9  227  32.6    232     365Waiting:        2  227  32.6    232     365Total:         10  227  31.3    232     368Percentage 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 我感觉很棒了。