需求和可行性
最近有这样的需求:
设置了生存时间的Key,在过期时能不能有所提示?
如果能对过期Key有个监听,如何对过期Key进行一个回调处理?
在知道 Redis 从2.8.0版本后,推出 Keyspace Notifications 特性后(参见我的上篇手记),对Key过期事件的处理,有了可能。
Key过期事件的Redis配置
这里需要配置 notify-keyspace-events 的参数为 “Ex”。x 代表了过期事件。
notify-keyspace-events "Ex"
保存配置后,重启Redis服务,使配置生效。
[root@chokingwin etc]# service redis-server restart /usr/local/redis/etc/redis.conf
Stopping redis-server: [ OK ]
Starting redis-server: [ OK ]
添加过期事件订阅
开启一个终端,redis-cli 进入 redis 。开始订阅所有操作,等待接收消息。
127.0.0.1:6379> psubscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
再开启一个终端,redis-cli 进入 redis,新增一个 10秒过期的键 name
127.0.0.1:6379> setex name 10 chokingwin
OK
10秒过期时间到时,另外一边执行了阻塞订阅操作后的终端,有如下信息输出
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "name"
顺利获取到过期键 name,说明对过期Key信息的订阅是成功的。
phpredis实现订阅Keyspace notification
phpredis 是 php 的一个扩展,使代码内可像客户端操作redis那样操作方便。
下面是对 phpredis 封装的一个类 redis.class.php
class MyRedis{
private $redis;
/**
* 构造函数
*
* @param string $host 主机号
* @param int $port 端口号
*/
public function __construct($host='127.0.0.1',$port=6379){
$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);
}
}// End Class
下面对上面的类进行一些实例化,以期达到过期事件的订阅。脚本 testRedisNotify.php 如下
require_once './redis.class.php';
$redis = new MyRedis();
$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, $pattern, $chan, $msg 四个参数为回调时返回的参数。
详细说明见下面 Redis 官方文档对 psubscribe 的说明。
/**
* Subscribe to channels by pattern
*
* @param array $patterns The number of elements removed from the set.
* @param stringarray $callback Either a string or an array with an object and method.
* The callback will get four arguments ($redis, $pattern, $channel, $message)
* @link http://redis.io/commands/psubscribe
* @example
*
* function psubscribe($redis, $pattern, $chan, $msg) {
* echo "Pattern: $pattern\n";
* echo "Channel: $chan\n";
* echo "Payload: $msg\n";
* }
*
*/
public function psubscribe( $patterns, $callback ) {}
因为订阅事件启动后是阻塞执行的,所以我们尝试在终端执行 testRedisNotify.php 这个脚本。
[root@chokingwin etc]# php testRedisNotify.php
新开一个终端,在 redis 里 新增一个 10秒过期 的键 chokingwin
127.0.0.1:6379> setex name 10 chokingwin
OK
10秒过期时间到时,另外一边执行了脚本被阻塞的终端,有如下信息输出
Pattern: __keyevent@0__:expired
Channel: __keyevent@0__:expired
Payload: name
说明利用 phpredis 对过期事件的订阅,是成功的。
补充
redis的客户端读取超时原因导致。
那么对 redis客户端进行一些参数设置,使读取超时参数 为 -1,表示不超时。
在 redis.class.php 里新增一个函数
public function setOption(){
$this->redis->setOption(Redis::OPT_READ_TIMEOUT,-1);
}
然后在脚本里调用这个 setOption() 函数。
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";
}
有个问题
做到这一步,利用 phpredis 扩展,成功在代码里实现对过期 Key 的监听,并在 psCallback()里进行回调处理。
开头提出的两个需求已经实现。
似乎一切都很顺利。
可是这里有个问题:redis 在执行完订阅操作后,终端进入阻塞状态,需要一直挂在那。且此订阅脚本需要人为在命令行执行,不符合实际需求。
实际上,我们对过期监听回调的需求,是希望它像守护进程一样,在后台运行,当有过期事件的消息时,触发回调函数。
使监听后台始终运行
希望像守护进程一样在后台一样,我是这样实现的。
Linux中有一个nohup命令。功能就是不挂断地运行命令。
同时nohup把脚本程序的所有输出,都放到当前目录的nohup.out文件中,如果文件不可写,则放到/nohup.out 文件中。那么有了这个命令以后,不管我们终端窗口是否关闭,都能够让我们的php脚本一直运行。
来看操作。
我们新建一个 nohupRedisNotify.php。
vim nohupRedisNotify.php
编写脚本同 testRedisNotify.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'
ps 确认一下脚本是否已在后台运行。查看进程结果如下:
[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 ps
脚本确实已经在 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
说明监听过期事件并回调成功。
至此,本次手记全部搞定。O(∩_∩)O哈哈~