Redis键值过期自动执行回调函数
用到了 redis 的键空间通知(keyspace notifications)
-
今天帮忙解决问题时遇到的redis一个功能点
-
具体行为就是:某个键值到了过期时间自动触发回调函数,然后执行一些操作,比如订单15分钟未支付就自动取消。
系统环境Win10, PHP7.1
-
下面记录下刚才爬的坑:
- 1、redis 2.8版本的升级到 3.0 以上再说,比如 3.2
- 2、如果将redis加入到windows的系统服务了,建议在初始阶段停止服务,使用redis-server "配置文件路径" 来启动redis,因为系统服务启动的redis使用的配置文件你可能搞不清楚,导致修改配置文件不生效,在最后我会讲。
- 3、各种路径,参数问题,加不加引号等问题。
-
本文仅就此功能进行讲解,业务逻辑不过多累述。
首先找一个空目录,新建四个文件,文件名以及内容如下:
- index.php 经过一些业务逻辑后,设置一个 10s 后过期的键值
<?php
require_once 'Redis2.class.php';
// require_once 'db.class.php';
$redis2 = new \Redis2();
// $mysql = new \mysql(); // 注释是你的业务逻辑
// $mysql->connect();
// $order_sn='SN'.time().'T'.rand(10000000,99999999);
// $data = ['ordersn'=>$order_sn,'status'=>0,'createtime'=>date('Y-m-d H:i:s',time())];
// $mysql->insert('order',$data);
$res = $redis2->setex('kkk',10, "It is no pay");
var_dump($res);exit;
- psubscribe.php 监听,超时回调函数
<?php
require_once 'Redis2.class.php';
ini_set('default_socket_timeout', -1); //不超时
$redis = new Redis2();
// 解决Redis客户端订阅时候超时情况
$redis->setOption();
$redis->psubscribe(array('__keyevent@0__:expired'), 'keyCallback');
// 回调函数,这里写处理逻辑
function keyCallback($redis, $pattern, $chan, $msg)
{
echo date('Y-M-D H:i:s'); // 这两行留作查看是否执行回调函数
// file_put_contents("d:/lizheng.log", "\n\n".print_r(123, true),8);
// 下面写你的业务逻辑
//修改订单状态
// $order=model('order')->where(['order_no'=>$msg])->find();
// $order->status=-1;
// $order->save();
// //库存还原
// $products=json_decode($order->snap_items,true);
// foreach($products as $v){
// model('product')->where(['id'=>$v['id']])->setInc('stock',$v['count']);
// }
}
- Redis2.class.php 封装 redis 类
<?php
class Redis2
{
private $redis;
public function __construct($host = '127.0.0.1', $port = 6379)
{
$this->redis = new Redis();
$this->redis->connect($host, $port);
// $this->redis->auth('123456');
}
public function setex($key, $time, $val)
{
return $this->redis->setex($key, $time, $val);
}
public function set($key, $val)
{
return $this->redis->set($key, $val);
}
public function get($key)
{
return $this->redis->get($key);
}
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);
}
public function lRange($key,$start,$end)
{
return $this->redis->lRange($key,$start,$end);
}
public function lPush($key, $value1, $value2 = null, $valueN = null ){
return $this->redis->lPush($key, $value1, $value2 = null, $valueN = null );
}
public function delete($key1, $key2 = null, $key3 = null)
{
return $this->redis->delete($key1, $key2 = null, $key3 = null);
}
}
- db.class.php 封装mysql, 这里我们不涉及业务逻辑所以未使用
<?php
class mysql
{
private $mysqli;
private $result;
/**
* 数据库连接
* @param $config 配置数组
*/
public function connect()
{
$config=array(
'host'=>'127.0.0.1',
'username'=>'root',
'password'=>'123456qwerty',
'database'=>'marhal',
'port'=>3306,
);
$host = $config['host']; //主机地址
$username = $config['username'];//用户名
$password = $config['password'];//密码
$database = $config['database'];//数据库
$port = $config['port']; //端口号
$this->mysqli = new mysqli($host, $username, $password, $database, $port);
}
/**
* 数据查询
* @param $table 数据表
* @param null $field 字段
* @param null $where 条件
* @return mixed 查询结果数目
*/
public function select($table, $field = null, $where = null)
{
$sql = "SELECT * FROM `{$table}`";
//echo $sql;exit;
if (!empty($field)) {
$field = '`' . implode('`,`', $field) . '`';
$sql = str_replace('*', $field, $sql);
}
if (!empty($where)) {
$sql = $sql . ' WHERE ' . $where;
}
$this->result = $this->mysqli->query($sql);
return $this->result;
}
/**
* @return mixed 获取全部结果
*/
public function fetchAll()
{
return $this->result->fetch_all(MYSQLI_ASSOC);
}
/**
* 插入数据
* @param $table 数据表
* @param $data 数据数组
* @return mixed 插入ID
*/
public function insert($table, $data)
{
foreach ($data as $key => $value) {
$data[$key] = $this->mysqli->real_escape_string($value);
}
$keys = '`' . implode('`,`', array_keys($data)) . '`';
$values = '\'' . implode("','", array_values($data)) . '\'';
$sql = "INSERT INTO `{$table}`( {$keys} )VALUES( {$values} )";
$this->mysqli->query($sql);
return $this->mysqli->insert_id;
}
/**
* 更新数据
* @param $table 数据表
* @param $data 数据数组
* @param $where 过滤条件
* @return mixed 受影响记录
*/
public function update($table, $data, $where)
{
foreach ($data as $key => $value) {
$data[$key] = $this->mysqli->real_escape_string($value);
}
$sets = array();
foreach ($data as $key => $value) {
$kstr = '`' . $key . '`';
$vstr = '\'' . $value . '\'';
array_push($sets, $kstr . '=' . $vstr);
}
$kav = implode(',', $sets);
$sql = "UPDATE `{$table}` SET {$kav} WHERE {$where}";
$this->mysqli->query($sql);
return $this->mysqli->affected_rows;
}
/**
* 删除数据
* @param $table 数据表
* @param $where 过滤条件
* @return mixed 受影响记录
*/
public function delete($table, $where)
{
$sql = "DELETE FROM `{$table}` WHERE {$where}";
$this->mysqli->query($sql);
return $this->mysqli->affected_rows;
}
}
配置文件修改
配置文件中找到 notify-keyspace-events "" 将其改成 notify-keyspace-events "Ex" // 注意引号, 之所以要求3.0以上版本,是因为2.8版本设置了此项后会于java冲突,无法启动。
Ex代表过期,另外注意修改的配置文件,是否为正使用的配置文件
- 以上操作完成后,重启redis,以指定配置文件的方式重启,以免使用其它的配置文件或redis默认配置。
redis-server "D:/redis/redis.windows.conf"; // 3.2版本为例,其它版本中,文件名可能为其它。
// 此处之外还有一个 redis.windows.service.conf 文件,作为系统服务启动时的配置文件。
-
服务截图如下
- "D:\Program Files\redis\redis-server.exe" --service-run redis.windows-service.conf --loglevel verbose
- "D:\Program Files\redis\redis-server.exe" --service-run redis.windows-service.conf --loglevel verbose
-
如果你不想将redis加入到系统服务,那么可以不用安装服务,只用命令行启动redis即可。
redis-server.exe --service-uninstall // redis系统服务的卸载, 也可以通过管理员命令行 sc delete 服务名 来卸载
redis-server --service-install redis.windows.conf --loglevel verbose // 安装服务
运行测试
- 切换到你的redis目录,或者将redis加入系统变量Path中
- php -v // 可以直接调用PHP命令,我的版本为7.1,此功能与php版本关系不大
- php psubscribe.php // 启动监听程序
- php index.php // 通过index.php 模拟订单超时,即建立一个生命周期为10秒的键值。
- 10秒后,psubscribe.php 会调用回调函数执行一些操作。
- psubscribe.php 窗口有相应的输出即可
知止而后有定,定而后能静,静而后能安,安而后能虑,虑而后能得。
所谓诚其意者,毋自欺也。