workerman v5
在經(jīng)歷了幾年的開發(fā)和測試,于2025年元旦正式發(fā)布,webman
開發(fā)框架及眾多周邊插件也緊隨其后更新兼容了v5版本;作為PHP界老牌的網(wǎng)絡容器,workerman
的穩(wěn)定性及易用性毋庸置疑,常駐內(nèi)存的運行模式、多進程、多協(xié)議支持等高性能高效率的特性讓許多PHPer接觸到了之前不曾觸摸過的技術方向;那么v5版本相較于之前版本給開發(fā)者帶來了什么比較明顯且用的上的特性呢?
revolt/event-loop
為基建的事件驅動庫以上是變化較大且意義重大的特性,除此之外還有許多的優(yōu)化內(nèi)容及特性升級,請參考官方文檔
我個人一直認為所謂‘協(xié)程’其實是至少包含了上下文管理、協(xié)程調(diào)度器、協(xié)程執(zhí)行器三部分的完整方案,在沒有Fiber
之前,原生PHP中其實已經(jīng)有了無棧協(xié)程的相關實現(xiàn),借助yield
完成,但不論是Fiber
還是yield
,都不是完整的‘協(xié)程’,只是和上下文相關的一些功能而已,并沒有調(diào)度及執(zhí)行兩部分。而非原生PHP中僅有swoole
是一套完整的協(xié)程實現(xiàn)方案,不論是它早期底層類yield
的異步還是現(xiàn)如今比較現(xiàn)代化的Coroutine
,它們都包含了成熟的事件循環(huán)驅動(調(diào)度/執(zhí)行)和豐富的上下文管理工具。
可能有人會問了,workerman
也有事件循環(huán)驅動,為什么沒有協(xié)程?先說結論,因為不好管理且生態(tài)不夠統(tǒng)一,生態(tài)的支持度也不足夠。
在沒有Fiber
之前,借助yield
來實現(xiàn)協(xié)程方案需要保存特別特別多的棧上下文,你可以把它們理解為因為手動中斷的入?yún)?shù),他們需要存放在比如靜態(tài)變量(也就是內(nèi)存)中等待下一次的喚醒,喚醒后繼續(xù)從中斷的地方執(zhí)行,再主動中斷的這個過程中,進程就可以交給其他的事務進行執(zhí)行,整個進程內(nèi)的所有不同事務呈現(xiàn)無序的交替運行狀態(tài),就像是我們?nèi)嗽诠ぷ鞯臅r候時不時去上個廁所、接杯咖啡,回來繼續(xù)工作;Fiber
其實也類似,只不過它并沒有像yield
一樣直接返回暫停時的棧上下文,而是主動保存在一個特定的地方自行管理,這樣就省去了自行使用內(nèi)存管理的問題,簡化了操作,在我每一次歸來后無需再翻看我的記事本查看我到底寫到那里了,而是直接就可以銜接。
workerman
之前也有利用yield
+ promise
包做的異步方案,但都要進行很多侵入式的改造,代價大于收益。
workerman v5
基于revolt/event-loop
作為事件驅動引擎,一方面是由此引入Fiber
,一方面是減少目前PHP開發(fā)中過多的事件驅動引擎的分化問題,另外還兼容了swow
、swoole
的事件驅動引擎,是支持中國本土化的內(nèi)容,另外本質上也是為了減少分化和加強協(xié)程的引入;就此,在workerman v5
中就可以使用以上三種驅動的協(xié)程方案。
那么協(xié)程能干什么呢?
假設一個場景,我們需要創(chuàng)建一個異步導出任務,這個異步導出任務可能的實現(xiàn)方式就會是:
- 前端請求接口 -> 接口創(chuàng)建任務 -> 將任務投遞至消息隊列
- 消息隊列消費導出 -> 修改任務狀態(tài)
- 前端輪詢查詢?nèi)蝿諣顟B(tài)及下載連接
而有了協(xié)程我們可以實現(xiàn)以下兩種方案:
自輪詢消費(查詢數(shù)據(jù)如果使用阻塞方法還是會阻塞)
public function test(): Response
{
$id = 'your_file_id';
// 根據(jù)請求參數(shù)獲取分片數(shù)量
$count = YourData::getShardingCount($request = request()->all());
for ($i = 0; $i < $count; $i++) {
// 分片獲取數(shù)據(jù)
if ($data = YourData::getListBySharding($request, $i)) {
// 導出追加
YourExcel::append($id, $data);
}
// 協(xié)程隨機出讓1-10 ms
Timer::sleep(rand(1, 10) / 1000);
}
return new Response(200, body: json_encode(YourExcel::getUrl($id)));
}
自輪詢查詢方式
public function test(): Response
{
$id = 'your_file_id';
// 發(fā)布至消息隊列
YourMessageMQ::publish($id, $request = request()->all());
while (1) {
// 查詢消息隊列消費情況
if (YourMessageMQ::isComplete($id)) {
break;
}
// 協(xié)程隨機出讓1-10 ms
Timer::sleep(rand(1, 10) / 1000);
}
return new Response(200, body: json_encode(YourMessageMQ::getReturn($id)));
}
以上兩種方式都不會特別的阻塞當前進程而實現(xiàn)一個長輪詢接口,避免了前端短輪詢的資源消耗問題;類似SSE的實現(xiàn)方式也會更簡單一些,只需要利用Timer::sleep
主動出讓當前協(xié)程控制權給事件驅動引擎就可以,而且這樣的好處是既保留了PHP系統(tǒng)層面的CPU控制權出讓sleep
/usleep
又提供了事件驅動控制權的出讓,區(qū)別于Swow
和Swoole
對系統(tǒng)函數(shù)的hook改造,但缺點又是無法改變已經(jīng)存在的阻塞式函數(shù)的阻塞調(diào)用邏輯。
因為PHP的協(xié)程方案是單線程的,同一時刻只能運行一個任務,所以需要在事件循環(huán)內(nèi)盡可能地non-blocking出讓控制權,才可能讓事件循環(huán)驅動在有限的時間內(nèi)執(zhí)行更多的任務;而目前PHP生態(tài)大多數(shù)的組件工具都是blocking的,協(xié)程所能覆蓋的業(yè)務范圍很窄,現(xiàn)存的很多協(xié)程組件并不能照顧大部分開發(fā)者的情緒,所以我真的希望在未來,PHP能夠涌現(xiàn)更多的開發(fā)者來貢獻協(xié)程相關的生態(tài),而不是分裂,希望在有限的時間和空間內(nèi),看到這門歷史不算太久的老編程語言能夠煥發(fā)青春。
任重道遠,也祝好
贊啊