在 OpenMix 全家桶 中有一個 Mix Redis Subscribe 的項目,這是一個不依賴 phpredis 擴展,直接解析 Redis 協議專用於訂閱處理的一個庫,任何 Swoole 框架均可使用,可普遍使用於 WebSocket 開發中,在 MixPHP 骨架中也默認包含了這個庫。php
爲什麼開發
MixPHP V2.1 完成開發後,我試圖開發一個基於訂閱機制的 WebScoket 服務,該服務須要可動態切換訂閱頻道,但 phpredis 的訂閱方法沒法實現如下功能:git
$redis = new \Redis(); $res = $redis->pconnect('127.0.0.1', 6379, 0); $redis->subscribe(['test'], function ($instance, $channelName, $message) { echo $channelName, "==>", $message, PHP_EOL; });
- 沒法得知訂閱成功
以上代碼中,當執行到 subscribe 會阻塞執行,只有在有消息過來時纔會執行到匿名函數中,並不會在訂閱成功的當時執行該閉包,可是 redis-cli 執行訂閱時,redis-server 是有回覆訂閱成功消息的,所以是 phpredis 的設計問題。github
- 沒法動態 subscribe 增長頻道
因爲 subscribe 阻塞了執行,代碼只能在有消息觸發回調時才能在回調中執行,所以動態增長頻道也是沒法操做的。golang
- unsubscribe 只可在回調中執行
由於上面那種阻塞回調的設計,若是須要取消一個頻道,只能在有消息過來時方可操做,可是實際需求一般是須要在任意時刻均可取消頻道。redis
- 沒法在其餘協程中 close 鏈接
phpredis 當試圖在匿名函數之外的其餘協程中 close 鏈接會拋出異常 PHP Fatal error: Uncaught RedisException: read error on connection
,這讓關閉一個訂閱中的 redis 鏈接都沒法優雅的實現。安全
造輪子
當我得知 redis 協議是簡單的文本協議時,我決定拋棄 phpredis 本身造一個好用的訂閱庫,新輪子具備如下優勢:閉包
- 不依賴 phpredis 擴展
- 平滑修改:可隨時增長、取消訂閱通道,實現無縫切換通道的需求。
- 跨協程安全關閉:可在任意時刻關閉訂閱。
- 通道獲取消息:該庫封裝風格參考 golang 語言 go-redis 庫封裝,經過 channel 獲取訂閱的消息。
$sub = new \Mix\Redis\Subscribe\Subscriber('127.0.0.1', 6379, '', 5); // 鏈接失敗將拋出異常 $sub->subscribe('foo', 'bar'); // 訂閱失敗將拋出異常 $chan = $sub->channel(); while (true) { $data = $chan->pop(); if (empty($data)) { // 手動close與redis異常斷開都會致使返回false if (!$sub->closed) { // redis異常斷開處理 var_dump('Redis connection is disconnected abnormally'); } break; } var_dump($data); }
接收到訂閱消息:框架
object(Mix\Redis\Subscribe\Message)#8 (2) { ["channel"]=> string(2) "foo" ["payload"]=> string(4) "test" }