Redis實踐操作之——keyspacenotification(鍵空間通知)
原始碼地址:https://github.com/Tinywan/PHP_Experience
一、需求分析:
- 設定了生存時間的Key,在過期時能不能有所提示?
- 如果能對過期Key有個監聽,如何對過期Key進行一個回撥處理?
- 如何使用 Redis 來實現定時任務?
二、序言:
本文所說的定時任務或者說計劃任務並不是很多人想象中的那樣,比如說每天凌晨三點自動執行起來跑一個指令碼。這種都已經爛大街了,隨便一個 Crontab 就能搞定了。
這裡所說的定時任務可以說是計時器任務,比如說使用者觸發了某個動作,那麼從這個點開始過二十四小時我們要對這個動作做點什麼。那麼如果有 1000 個使用者觸發了這個動作,就會有 1000 個定時任務。於是這就不是 Cron 範疇裡面的內容了。
舉個最簡單的例子,一個使用者推薦了另一個使用者,我們定一個二十四小時之後的任務,看看被推薦的使用者有沒有來註冊,如果沒註冊就給他搞一條簡訊過去。
三、Redis介紹
在 Redis 的 2.8.0 版本之後,其推出了一個新的特性——鍵空間訊息(Redis Keyspace Notifications),它配合 2.0.0 版本之後的 SUBSCRIBE 就能完成這個定時任務
的操作了,不過定時的單位是秒。
(1)Publish / Subscribe
Redis 在 2.0.0 之後推出了 Pub / Sub 的指令,大致就是說一邊給 Redis 的特定頻道傳送訊息,另一邊從 Redis 的特定頻道取值——形成了一個簡易的訊息佇列。
(2)Redis Keyspace Notifications
在 Redis 裡面有一些事件,比如鍵到期、鍵被刪除等。然後我們可以通過配置一些東西來讓 Redis 一旦觸發這些事件的時候就往特定的 Channel 推一條訊息。
大致的流程就是我們給 Redis 的某一個 db 設定過期事件,使其鍵一旦過期就會往特定頻道推訊息,我在自己的客戶端這邊就一直消費這個頻道就好了。
以後一來一條定時任務,我們就把這個任務狀態壓縮成一個鍵,並且過期時間為距這個任務執行的時間差。那麼當鍵一旦到期,就到了任務該執行的時間,Redis 自然會把過期訊息推去,我們的客戶端就能接收到了。這樣一來就起到了定時任務的作用。
四、Key過期事件的Redis配置
這裡需要配置 notify-keyspace-events 的引數為 “Ex”。x 代表了過期事件。notify-keyspace-events “Ex” 儲存配置後,重啟Redis服務,使配置生效。
重啟Reids伺服器:
root@iZ23s8agtagZ:/etc/redis# service redis-server restart redis.conf
Stopping redis-server: redis-server.
Starting redis-server: redis-server.
新增過期事件訂閱 開啟一個終端,redis-cli 進入 redis 。開始訂閱所有操作,等待接收訊息。
tinywan@iZ23a7607jaZ:~$ redis-cli -h 127.0.01.4 -p 63789
127.0.0.1:63789> psubscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
再開啟一個終端,redis-cli 進入 redis,新增一個 20秒過期的鍵:
1270.01.1.1:63789> SETEX coolName 123 20
OK
121.41.188.109:63789> get coolName
"20"
121.41.188.109:63789> ttl coolName
(integer) 104
另外一邊執行了阻塞訂閱操作後的終端,20秒過期後有如下資訊輸出:
121.141.188.209:63789> psubscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "coolName"
說明:說明對過期Key資訊的訂閱是成功的。
五、PHPREDIS實現訂閱Keyspace notification
Redis例項化類:(RedisInstance.class.php)
<?php
class RedisInstance
{
private $redis;
public function __construct($host = `121.41.88.209`, $port = 63789)
{
$this->redis = new Redis();
$this->redis->connect($host, $port);
}
public function expire($key = null, $time = 0)
{
return $this->redis->expire($key, $time);
}
public function psubscribe($patterns = array(), $callback)
{
$this->redis->psubscribe($patterns, $callback);
}
public function setOption()
{
$this->redis->setOption(Redis::OPT_READ_TIMEOUT,-1);
}
}
過期事件的訂閱:(psubscribe.php)
<?php
require_once `./RedisInstance.class.php`;
$redis = new RedisInstance();
// 解決Redis客戶端訂閱時候超時情況
$redis->setOption();
$redis->psubscribe(array(`__keyevent@0__:expired`), `psCallback`);
// 回撥函式,這裡寫處理邏輯
function psCallback($redis, $pattern, $chan, $msg)
{
echo "Pattern: $pattern
";
echo "Channel: $chan
";
echo "Payload: $msg
";
}
說明:psCallback 函式為訂閱事件後的回撥函式。$redis, $pattern, $chan, $msg 四個引數為回撥時返回的引數。 詳細說明見下面 Redis 官方文件對 psubscribe 的說明。
因為訂閱事件啟動後是阻塞執行的,所以我們嘗試在終端執行 psubscribe.php 這個指令碼。
D:wampwww
edistest>php psubscribe.php
設定一個過期事件:
121.41.188.109:63789> SETEX username123 20
20秒過期時間到時,另外一邊執行了指令碼被阻塞的終端,有如下資訊輸出訂閱結果:
D:wampwww
edistest>php psubscribe.php
Pattern: __keyevent@0__:expired
Channel: __keyevent@0__:expired
Payload: username123
以上PHP操作Reids是成功的
六、使監聽後臺始終執行(訂閱)
有個問題 做到這一步,利用 phpredis 擴充套件,成功在程式碼裡實現對過期 Key 的監聽,並在 psCallback()裡進行回撥處理。 開頭提出的兩個需求已經實現。 可是這裡有個問題:redis 在執行完訂閱操作後,終端進入阻塞狀態,需要一直掛在那。且此訂閱指令碼需要人為在命令列執行,不符合實際需求。
實際上,我們對過期監聽回撥的需求,是希望它像守護程式一樣,在後臺執行,當有過期事件的訊息時,觸發回撥函式。 使監聽後臺始終執行 希望像守護程式一樣在後臺一樣,
我是這樣實現的。
Linux中有一個nohup命令。功能就是不結束通話地執行命令。 同時nohup把指令碼程式的所有輸出,都放到當前目錄的nohup.out檔案中,如果檔案不可寫,則放到<使用者主目錄>/nohup.out 檔案中。那麼有了這個命令以後,不管我們終端視窗是否關閉,都能夠讓我們的php指令碼一直執行。
編寫PHP指令碼檔案:
<?php
#! /usr/local/php/bin/php
require_once `./redis.class.php`;
$redis = new MyRedis();
$redis->setOption();
$redis->psubscribe(array(`__keyevent@0__:expired`), `psCallback`);
// 回撥函式,這裡寫處理邏輯
function psCallback($redis, $pattern, $chan, $msg)
{
echo "Pattern: $pattern
";
echo "Channel: $chan
";
echo "Payload: $msg
";
}
注意:不過我們在開頭,需要申明 php 編譯器的路徑:#! /usr/local/php/bin/php 。 這是執行 php 指令碼所必須的。
然後,nohup 不掛起執行 nohupRedisNotify.php,注意 末尾的 &
[root@chokingwin HiGirl]# nohup ./nohupRedisNotify.php & [1] 4456 nohup: ignoring input and appending output to `nohup.out`
確認一下指令碼是否已在後臺執行。檢視程式結果如下:
[root@chokingwin HiGirl]# ps PID TTY TIME CMD 3943 pts/2 00:00:00 bash 4456 pts/2 00:00:00 nohupRedisNotif 4480 pts/2 00:00:00
說明:指令碼確實已經在 4456 號程式上跑起來。
最後在檢視下nohup.out cat 一下 nohuo.out,看下是否有過期輸出:
[root@chokingwin HiGirl]# cat nohup.out [root@chokingwin HiGirl]#
並沒有。我們還是老樣子,新增一個10秒過期的的鍵 name。10秒後,我們再 cat 一次。
[root@chokingwin HiGirl]# cat nohup.out Pattern: __keyevent@0__:expired Channel: __keyevent@0__:expired Payload: name
說明監聽過期事件並回撥成功。
nohup命令:記錄詳情:
nohup沒有輸出的情況:
sudo nohup nohupRedisNotify.php > /dev/null 2>&1 &
檢視jobs程式ID:[ jobs -l ]命令
www@iZ232eoxo41Z:~/tinywan $ jobs -l
[1]- 1365 Stopped (tty output) sudo nohup nohupRedisNotify.php > /dev/null 2>&1
[2]+ 1370 Stopped (tty output) sudo nohup nohupRedisNotify.php > /dev/null 2>&1
linux kill程式殺不掉(解決辦法):
kill -9 PID
www@iZ232eoxo41Z:~/tinywan $ sudo kill -9 1370
[2]+ Killed sudo nohup nohupRedisNotify.php > /dev/null 2>&1
如果是以後執行的後臺程式的,命令【jobs -l】是沒辦法察覺任務以及PID的,這時候可以藉助:ps aux | grep nohup 篩選哦,在使用 kill PID 剷除即可
在這裡是www使用者已root執行的任務,用jobs -l
jobs -l
www@iZ232eoxo41Z:~ $ jobs -l
www@iZ232eoxo41Z:~ $
ps aux | grep nohup
www@iZ232eoxo41Z:~ $ ps aux | grep nohup
root 18448 0.0 0.2 65160 2088 ? S 14:44 0:00 sudo nohup php ./nohupRedisNotify.php
root 18449 0.0 1.5 283368 15956 ? S 14:44 0:00 php ./nohupRedisNotify.php
www 18605 0.0 0.0 11740 928 pts/1 S+ 14:50 0:00 grep --color=auto nohup
經驗分享環節:
1、今天在Linux伺服器執行一個php指令碼nohup.php 掛起一個Redis訂閱事件的時候,發現每次都不會呼叫API介面傳遞引數。但是直接執行的話(php nohup.php)的時候是 可以呼叫介面的,經過檢查發現是許可權的問題:
分析:當前登入使用者為www使用者:但是啟動指令碼的時候確實這樣的(Root身份執行):(可以在命令列直接執行,列印過期的事件key,並且輸出可以作為除錯哦!)
sudo nohup php nohupRedisNotify.php > /dev/null 2>&1 &
修改後的:
nohup php nohupRedisNotify.php > /dev/null 2>&1 &
這樣的話就直接可以回撥自己寫的API介面啦!
相關文章
- 大神教你實現redis鍵空間通知Redis
- Rust工作空間(workspace)實踐Rust
- 解決Redis叢集條件下鍵空間通知伺服器接收不到訊息的問題Redis伺服器
- 【TABLESPACE】Oracle表空間最佳實踐Oracle
- Redis原理及實踐之GeoHashRedis
- Notification桌面通知實踐
- 操作Redis之go-redisRedisGo
- 操作Redis之redigoRedisGo
- kubernetes生產實踐之redis-clusterRedis
- redis入門指南(四)—— redis如何節省空間Redis
- Redis核心原理與實踐--列表實現原理之ziplistRedis
- Go操作Redis實戰GoRedis
- Redis 地理空間(geospatial)介紹及應用Redis
- mysql之 表空間傳輸MySql
- 深入淺出之切空間
- JavaScript之記憶體空間JavaScript記憶體
- Redis使用與實踐Redis
- redis實踐及思考Redis
- 技術應用丨DWS 空間釋放(vacuum full) 最佳實踐
- Redis核心原理與實踐--列表實現原理之quicklist結構RedisUI
- Oracle切換undo表空間操作步驟Oracle
- 人工智慧賦能網路空間安全報告:模式與實踐人工智慧模式
- 智慧空間亮相高交會,解析園區的創新與實踐
- SYSAUX表空間清理之SM/OPTSTATUX
- Spring Data Redis 最佳實踐!SpringRedis
- MySQL 執行 Online DDL 操作報錯空間不足?MySql
- kubernetes實踐之十八:叢集各模組之間的通訊
- Redis 實戰 —— 02. Redis 簡單實踐 - 文章投票Redis
- PHP 操作 Redis 之 phpredis 擴充套件PHPRedis套件
- 騰訊雲操作實踐
- 執行緒間通訊_等待/通知之Thread.join()執行緒thread
- 綠盟科技實力護航湖北省網路空間安全實踐能力競賽
- Spring Boot整合Redis實戰操作Spring BootRedis
- go 學習筆記之工作空間Go筆記
- Laravel5.6使用redis佇列實現系統通知LaravelRedis佇列
- 16、表空間 建立表空間
- 《Java架構師的最佳實踐》生產環境JVM調優之空間擔保失敗引起的FullGCJava架構JVMGC
- Redis 實戰 —— 03. Redis 簡單實踐 - Web應用RedisWeb
- 《英雄聯盟》手遊試玩:具備操作空間