問題一:啟用監聽收不到過期時間消息,原因是未開啟配置
解決辦法是 在redis配置文件內開啟 notify-keyspace-events Ex或者在redis命令行 redis-cli 使用命令:
config set notify-keyspace-events Ex
問題二:PredisConnectionConnectionException : Error while reading line from the server
原因是Redis默認鏈接時間未60秒,在database.php設置read_write_timeout為0即可。
"read_write_timeout"=>0
問題三:ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
這個是因為一個Redis鏈接使用監聽時,無法使用其他命令。需要重新建立一個鏈接。期初我使用 new \Predis\Client(),一直報錯,我也不知道為啥。然后我想到了使用集群,使用相同配置。將監聽事件設置為單獨實例。具體操作如下:
//datebase.php配置頁面 'redis' => [ 'client' => 'predis', 'default' => [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => 0, "queue" => '{default}',//queue站點默認走的redis ], 'publisher' => [ //redis 訂閱監聽 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => 0, "read_write_timeout"=>0,//長連接不要斷 ], ] //監聽頁面 //__keyevent@*__:expired監聽過期消息 $redis=Redis::connection('publisher');//創建新的實例 $redis->psubscribe(['__keyevent@*__:expired'], function ($message, $channel) { echo $message; Redis::set('aa','123');//這樣就不會報錯了。這里使用的是default的,是兩個redis鏈接。 });
Pub/Sub:
"發布/訂閱"在redis中,被設計的非常輕量級和簡潔,它做到了消息的“發布”和“訂閱”的
基本能力;但是尚未提供關於消息的持久化等各種企業級的特性。
一個Redis client發布消息,其他多個redis client訂閱消息,發布的消息“即發即失”,redis
不會持久保存發布的消息;消息訂閱者也將只能得到訂閱之后的消息,通道中此前的消息將無
從獲得。
消息發布者,即publish客戶端,無需獨占鏈接,你可以在publish消息的同時,使用同一個redis-client鏈接進行其他操作(例如:INCR等)
消息訂閱者,即subscribe客戶端,需要獨占鏈接,即進行subscribe期間,redis-client無法穿插其他操作,
此時client以阻塞的方式等待“publish端”的消息;因此這里subscribe端需要使用單獨的鏈接,甚至需要在額外的線程中使用。
Tcp默認連接時間固定,如果在這時間內sub端沒有接收到pub端消息,或pub端沒有消息產生,sub端的連接都會被強制回收,
這里就需要使用特殊手段解決,用定時器來模擬pub和sub之間的保活機制,定時器時間不能超過TCP最大連接時間,具體根據機器環境來定;
一旦subscribe端斷開鏈接,將會失去部分消息,即鏈接失效期間的消息將會丟失,所以這里就需要考慮到借助redis的list來持久化;
如果你非常關注每個消息,那么你應該基於Redis做一些額外的補充工作,如果你期望訂閱是持久的,那么如下的設計思路可以借鑒:
1) subscribe端:
首先向一個Set集合中增加“訂閱者ID”, 此Set集合保存了“活躍訂閱”者,
訂閱者ID標記每個唯一的訂閱者,此Set為 "活躍訂閱者集合"
2) subcribe端開啟訂閱操作,並基於Redis創建一個以 "訂閱者ID" 為KEY的LIST數據結構,
此LIST中存儲了所有的尚未消費的消息,此List稱為 "訂閱者消息隊列"
3) publish端:
每發布一條消息之后,publish端都需要遍歷 "活躍訂閱者集合",並依次
向每個 "訂閱者消息隊列" 尾部追加此次發布的消息.
4) 到此為止,我們可以基本保證,發布的每一條消息,都會持久保存在每個 "訂閱者消息隊列" 中.
5) subscribe端,每收到一個訂閱消息,在消費之后,必須刪除自己的 "訂閱者消息隊列" 頭部的一條記錄.
6) subscribe端啟動時,如果發現自己的 "訂閱者消息隊列" 有殘存記錄, 那么將會首先消費這些記錄,然后再去訂閱.
以上方法可以保證成功到達的消息必消費不丟失;
但還是會存在ngx業務機方自丟失數據問題,也就是ngx業務機自身問題或網絡問題導致ngx業務機發布的消息沒有送達redis機器;
更完善的確認機制才能徹底解決上述存在問題;
注意,在實際ngx_lua_redis應用中,redis單個客戶端訂閱模式下僅能使用有限的幾個命令,不能使用其它結構命令,如lpop,rpush等;
因為 publish是普通的request/response模式, 但subscribe不是,否則會報錯:
ERR only (P)SUBSCRIBE \/ (P)UNSUBSCRIBE \/ PING \/ QUIT allowed in this cont
關於這點以下是官網一般解釋:
You are required to use two connections for pub and sub. A subscriber connection cannot issue any commands
other than subscribe, psubscribe, unsubscribe, punsubscribe (although @Antirez has hinted of a subscriber-safe
ping in the future). If you try to do anything else, redis tells you:
-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
(note that you can't test this with redis-cli, since that understands the protocol well enough to prevent you
from issuing commands once you have subscribed - but any other basic socket tool should work fine)
This is because subscriber connections work very differently - rather than working on a request/response basis,
incoming messages can now come in at any time, unsolicited.
publish is a regular request/response command, so must be sent on a regular connection, not a subscriber connection.