PHP如何解決百萬級全站使用者訊息推送問題

語言: CN / TW / HK

點選進入“PHP開源社群”    

免費獲取進階面試、文件、視訊資源

一、問題場景描述

基於Swoole的WebSocket服務對站內的訊息進行的推送,有個全站進行站內訊息的推送很棘手,因為峰值的使用者服務1600+/QPS,伺服器的配置2核8G(的配置)。難點在於:Http的服務是接收主站的請求,需要及時返回,響應時間不能久。Redis的執行時間不能太久,(Redis是單程序)慢請求會卡主其他的使用。百萬級使用者場景,全站使用者傳送時間不定,舊版本是Crontab實現的,因為後臺直接請求websocket服務改動大

以上諸多難題。

二、解決思路

難題有兩個思路,先從業務場景分析,在從技術角度下手,因為全站訊息通知是很久才會使用一次,有站內信做兜底,需要以保證業務可用性和效能,需要找到一個適中的策略。( 插播一條廣告:需要開通正版 JetBrains全家桶 的可以聯絡我,56一年,正版授權,官網可查有效期,有需要的加我微信:fuwei18369備註:817) 需要在接收訊息、執行任務(Crontab)的時候分別想辦法。Http接收服務中加分散式鎖,粒度15分鐘,15分鐘不管提交多少次,都執行有且只有一次。

public function systemMessage(){

//驗證引數...略


$lock = \EasySwoole\RedisPool\RedisPool::invoke(function (\EasySwoole\Redis\Redis $redis){

$lock = $redis->get('systemMessageLock');

return $lock;

},self::REDIS_CONN_NAME );



if($lock){

return $this->writeJson(Status::CODE_OK,[],Status::getReasonPhrase(Status::CODE_OK));

}

//沒有鎖,繼續下面的操作

}

Crontab 的執行粒度是1分鐘,也就是說最大延遲59s是可以接收的,這裡的難於處理的在於第一次沒有執行完畢,又執行了第二次,以此類推,這樣是不可控的,下面是測試的結果,debug超出記憶體大小限制了。

[2022-02-22 03:00:23][debug][error] : [Allowed memory size of 134217728 bytes exhausted

還有task重複執行的問題,說明第一次沒有執行完,又執行了下一次…果然和之前設想的一樣…

$taskId:5workIndex:1

$taskId:3workIndex:2

$taskId:3workIndex:2

$taskId:8workIndex:0

$taskId:8workIndex:0

$taskId:1workIndex:3

解決思路:對指令碼執行時間和使用記憶體進行設定,以防止執行任務時處於可控中,業務程式碼中加鎖,以防止計算任務的重複執行。

對鎖回收進行補償處理,如果15分鐘沒有回收,鎖主動釋放,附上Demo。

function run( $taskId, $workerIndex)

{

ini_set('memory_limit', '1024M');

set_time_limit(0);

//讀取佇列中的資料

$redis = \EasySwoole\RedisPool\RedisPool::defer('redis');

$taskIdLock = $redis->get($this->taskLock);

if($taskIdLock){

return ;

}



$json = $redis->lPop(self::PUSH_MSG_NOTICE_SYSTEM);

if($json && !$taskIdLock){

$redis->setEx($this->taskLock,900,$taskId);

}



if(isset($json) && !empty($json)){

$server = ServerManager::getInstance()->getSwooleServer();

$start_fd = 0;



while(true)

{

$conn_list = $server->getClientList( $start_fd, $this->limit );

if ($conn_list===false || count($conn_list) === 0 || empty($conn_list))

{

//刪除鎖

\EasySwoole\RedisPool\RedisPool::invoke(function (\EasySwoole\Redis\Redis $redis) {

$redis->del($this->taskLock);

}, self::REDIS_CONN_NAME);

break;

}

$start_fd = end($conn_list);

foreach ($conn_list as $fd){

$server->push($fd, json_encode($this->pushMsg));

}

}

}

}

*宣告:本文於網路整理,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。

END

PHP開源社群

掃描關注  進入”PHP資料“

免費獲取進階

面試、文件、視訊資源

點選“檢視原文”獲取更多