Swoole?webSocket客服IM消息系統(tǒng)方案解析
概述
基于Swoole的websocket服務(wù),再之前的消息系統(tǒng)系列的第4篇,實現(xiàn)了更加復雜的業(yè)務(wù)場景,是對消息推送的完善和優(yōu)化,代碼本身就是不斷自我優(yōu)化的過程。
實現(xiàn)方案
技術(shù)的實現(xiàn)方案點主要PMQ,2組客戶端(用戶端、客服管理端),3個主要的部分組成(Push推送消息+Pull拉取未讀消息+MessageQueue消息隊列),具體流程和交互方式見上面的架構(gòu)流程圖。
1.建立鏈接,借鑒Tcp3次握手的原理,將每一次的用戶詢問新增一個關(guān)系,詢問結(jié)束時再將關(guān)系釋放,因為每次隨機分配的客服是不一致的,客服管理員控制臺,進入控制臺會觸發(fā)檢測客服映射關(guān)系的程序,以保證關(guān)系的唯一性。
2.客服分配:客服分配是根據(jù)用戶是否為第一次進入鏈接進行判定依據(jù),首次會隨機分發(fā)配給在線客服中的其中一個,如果之前分配過的客服也在線,優(yōu)化分配存在客服,這樣處理的原因是客服不易變,用戶異變,防止反復鏈接/斷開操作,減少網(wǎng)絡(luò)開銷。
3.并發(fā)鎖:相同用戶在同一時間有3s的鎖定狀態(tài),用來防止關(guān)系錯亂,在客戶端發(fā)來請求時優(yōu)先獲取緩存,近少可能的訪問數(shù)據(jù)庫,提高服務(wù)的穩(wěn)定性和性能。
//設(shè)置分布式鎖,3s之內(nèi)只能請求一次 $lock = RedisPool::invoke(function (Redis $redis) use ($toUid) { return $redis->get(Category::$openLock . $toUid); }, self::REDIS_CONN_NAME); if ($lock) { $msgErrorRet['code'] = 416; $msgErrorRet['msg'] = 'Please try again'; return $this->response()->setMessage(json_encode($msgErrorRet)); } //查詢是否存在鏈接關(guān)系 $imUserRelation = RedisPool::invoke(function (Redis $redis) use ($toUid) { $redis->setEx(Category::$openLockPrefix . $toUid, 3, $toUid); return $redis->get(Category::$imUserRelationName . $toUid); }, self::REDIS_CONN_NAME);
4.網(wǎng)絡(luò)異常處理,回收服務(wù):針對App崩潰、網(wǎng)絡(luò)異常斷開的鏈接,主動監(jiān)聽斷開的fd,進行關(guān)系處理,對所有斷開鏈接的websocket,進行回收,清除關(guān)系。
static function onClose(\swoole_server $server, int $fd, int $reactorId) { $info = $server->getClientInfo($fd); $fd = intval($fd); if ($info && $info['websocket_status'] === WEBSOCKET_STATUS_FRAME) { TaskManager::getInstance()->async(function () use ($fd) { RedisPool::invoke(function (Redis $redis) use ($fd) { //回收用戶 $uid = $redis->hGet('PUSH_MSG_SOCKET_FD', $fd); if (isset($uid) && !empty($uid) && is_numeric($uid)) { $redis->zRem('PUSH_MSG_USER_LOGIN', $fd); //檢測是否有客服關(guān)系未斷開 $redis->del(Category::$imUserRelationName . $uid); $redis->hDel('PUSH_MSG_SOCKET_FD', $fd); } //回收客服管理用戶 $cUid = $redis->hGet('PUSH_CUSTOMER_MSG_SOCKET_FD', $fd); if (isset($cUid) && !empty($cUid)) { $redis->zRem('PUSH_CUSTOMER_MSG_USER_LOGIN', $fd); $redis->hDel('PUSH_CUSTOMER_MSG_SOCKET_FD', $fd); } }, 'redis'); }); } }
5.獲取離線消息分配算法,按照客服管理員在線人數(shù),把離線消息按照用戶來重新組裝,平均分配給在線管理員,如果數(shù)量不能被整除,也不會造成分配不均情況。
//驗證客服管理員在線 $vUid = []; $server = ServerManager::getInstance()->getSwooleServer(); foreach ($virtualUid as $fd => $vid){ $info = $server->getClientInfo($fd); if ($info && $info['websocket_status'] == 3) { $vUid[$fd] = $vid; } } if (!empty($pullData) && !empty($vUid)) { $uIds = array_keys($pullData); $row = ceil(count($uIds) / count($vUid)); $share = array_chunk($uIds, $row, true); $keyDict = $vUid; $pushList = []; // code 組裝代碼略... }
6.websocket對象不回收:從控制臺打開新窗口時,就會新增一個websocket對象,后來在瀏覽器中刷新處理的,沒有找到回收的辦法。
7.心跳:客服的websocket心跳使用的是實時push消息,5s循環(huán)一次,防止鏈接斷開,服務(wù)下線。
實踐Swoole里的坑
鏈接數(shù)變化正常,但是內(nèi)存好像沒有得到很好的釋放,而且進程里也出現(xiàn)了很多野進程,野進程多可能存在的原因是這樣的,你沒有守護啟動,然后主進程掛了,后面的進程找不到父進程,變成了僵尸進程或者是孤兒進程。
內(nèi)存也不對勁,大概率是我執(zhí)行腳本里出了問題,去掉了修改配置的語句,在Base類里加入了unset,及時釋放掉內(nèi)存。
出現(xiàn)問題的解決的原因是我在Crontab腳本里加了結(jié)束時長造成的問題,cli模式下的php生命周期進程得不到釋放造成的,合理使用Swoole中的協(xié)程就好了。
ini_set('memory_limit', '1024M'); set_time_limit(0);
成果
上線2年的時間里,進行了5次升級和優(yōu)化,活躍用戶10w+,最高峰值6w/s,130w/h訪問量,是一個非常成功的實踐結(jié)果。
用最簡單的技術(shù)實現(xiàn)方式,節(jié)省企業(yè)成本,減少系統(tǒng)開發(fā)和維護成本,提高辦公效率才是技術(shù)人應(yīng)該做的事兒,做解決實際復雜業(yè)務(wù)解決方案并落地的技術(shù)人,En。
以上就是Swoole webSocket客服IM消息系統(tǒng)方案解析的詳細內(nèi)容,更多關(guān)于Swoole webSocket IM消息的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Laravel框架DB facade數(shù)據(jù)庫操作詳解
這篇文章主要介紹了Laravel框架DB facade數(shù)據(jù)庫操作,結(jié)合實例形式詳細分析了laravel數(shù)據(jù)庫基本創(chuàng)建、連接、增刪改查等操作技巧,需要的朋友可以參考下2019-12-12Symfony2實現(xiàn)從數(shù)據(jù)庫獲取數(shù)據(jù)的方法小結(jié)
這篇文章主要介紹了Symfony2實現(xiàn)從數(shù)據(jù)庫獲取數(shù)據(jù)的方法,結(jié)合實例形式總結(jié)分析了常用的Symfony數(shù)據(jù)庫查詢技巧,需要的朋友可以參考下2016-03-03laravel框架創(chuàng)建授權(quán)策略實例分析
這篇文章主要介紹了laravel框架創(chuàng)建授權(quán)策略,結(jié)合實例形式分析了laravel框架創(chuàng)建授權(quán)策略的相關(guān)步驟、實現(xiàn)方法與操作注意事項,需要的朋友可以參考下2019-11-11PHP網(wǎng)頁游戲?qū)W習之Xnova(ogame)源碼解讀(五)
這篇文章主要介紹了PHP網(wǎng)頁游戲Xnova(ogame)源碼解讀游戲主界面,需要的朋友可以參考下2014-06-06PHP性能優(yōu)化工具篇Benchmark類調(diào)試執(zhí)行時間
這是PHP性能優(yōu)化系列第二期,如何使用PEAR工具類Benchmark逐行獲取代碼或函數(shù)的執(zhí)行時間2011-12-12