共计 6786 个字符,预计需要花费 17 分钟才能阅读完成。
webman 是一款基于 workerman 开发的 http 服务框架,用于开发 web 站点或者 http 接口。反对路由、中间件、主动注入、多利用、自定义过程、无需更改间接兼容现有 composer 我的项目组件等诸多个性。具备学习成本低、简略易用、超高性能、超高稳定性等特点。https://www.workerman.net/doc…
简略来说,webman
是基于 workerman
的一款常驻内存的 利用
服务框架,运行模式为多过程阻塞模式,IO 模型必定是多路复用
,至于是select/poll
还是 epoll
应该同 workerman
的场景统一,看是否装置了 event
扩大了(倡议装置,高并发下 epoll
模型更具劣势)。
尽管不像以后许多基于 swoole
的 协程
或 相似 node/reactPHP
等 eventLoop
的异步非阻塞模式的框架,但基于 epoll
模型时,开 cpu
个 worker
单机 C10K
也没什太大鸭梨。
小课堂
单过程模式
一个服务过程,来一个申请就阻塞,解决期间回绝响应其余申请。
1、开始期待以后申请 网络 IO
实现。
2、紧接着解决代码业务(期间可能也会随同着各种 网络 IO
,你的业务网代码总不能只是 "hello world"
吧,数据库 IO
、文件 IO
、调用其余微服务的 网络 IO
,都会产生阻塞)。
3、发送响应结束。能够持续接管解决下一个申请。
毛病:无奈承载高并发,你将会收到各种 502
响应。
多线程 / 协程模式
一个主服务过程,来一个申请就创立一个线程去专用解决,线程专一解决负责的申请。相比单过程模式,能够承载较高的申请并发量,但创立和切换线程的开销也是很大的,还有死锁的问题(当初又有了 协程
,用户态线程,更加轻量级,还能够)。
IO 多路复用模式
IO 多路复用模式下,worker
过程在接管一个申请后,如果该申请还未就绪(内核还未实现 socket
数据的读取及未 copy
至用户态),那么 worker
是能够持续去接管其余申请的,当某申请的 socket
数据读取实现后,worker
便开始执行业务解决(留神:此阶段 worker
是被业务解决独占的,期间无奈解决其余申请)。业务解决实现,worker
被开释,复原最后的状态流程。select/poll
和 epoll
都是 IO 多路复用
,不同之处在于 epoll
采纳更敌对的告诉机制,select/poll
要被动的忙轮训来监测是否有已就绪的申请socket
,epoll
则是期待内核的被动告诉。
EventLoop 模式
node
,reactPHP
是比拟典型的代表。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.
当初查看 mysql
的 processlist
并不会有 webman
的 worker
建设的链接,因为链接会在 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
没有数据库连接池呢?
因为 webman
的 worker
工作模式为 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
我感觉很棒了。