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 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.
当初查看 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: 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
我感觉很棒了。