Redis的keyspace notification(鍵空間通知)


文章來源https://www.cnblogs.com/tinywan/p/5903988.html

一、需求分析:

  1. 設置了生存時間的Key,在過期時能不能有所提示?
  2. 如果能對過期Key有個監聽,如何對過期Key進行一個回調處理?
  3. 如何使用 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\n"; echo "Channel: $chan\n"; echo "Payload: $msg\n\n"; }
復制代碼

說明:psCallback 函數為訂閱事件后的回調函數。redis,redis,pattern, chan,chan,msg 四個參數為回調時返回的參數。 詳細說明見下面 Redis 官方文檔對 psubscribe 的說明。

因為訂閱事件啟動后是阻塞執行的,所以我們嘗試在終端執行 psubscribe.php 這個腳本。

D:\wamp\www\redistest>php psubscribe.php

設置一個過期事件:

121.41.188.109:63789> SETEX username123 20

20秒過期時間到時,另外一邊執行了腳本被阻塞的終端,有如下信息輸出訂閱結果:

D:\wamp\www\redistest>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\n";
    echo "Channel: $chan\n";
    echo "Payload: $msg\n\n";
}
復制代碼

注意:不過我們在開頭,需要申明 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接口啦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM