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配置
1、这里需要配置 notify-keyspace-events 的参数为 “Ex”。x 代表了过期事件。notify-keyspace-events “Ex” 保存配置后,重启Redis服务,使配置生效。
2、安装Redis
PS D:\Redis> redis-server --service-install redis.windows.conf --loglevel verbose
结果:
[13592] 20 Jan 15:46:09.313 # Granting read/write access to 'NT AUTHORITY\NetworkService' on: "D:\Redis" "D:\Redis\"
[13592] 20 Jan 15:46:09.313 # Redis successfully installed as a service.
3、开启Redis
PS D:\Redis> redis-server --service-start
结果:
[14104] 20 Jan 15:46:52.139 # Redis service successfully started.
4、测试订阅是否成功
4.1 开启一个终端client01
4.2 开启另外一个终端client02
4.3 等过了10秒后,再看终端client01
说明:说明对过期Key信息的订阅是成功的。
PHP实现订阅代码
1、数据库操作类(db.class.php)
<?php
class mysql
{
private $mysqli;
private $result;
/**
* 数据库连接
* @param $config 配置数组
*/
public function connect()
{
$config=array(
'host'=>'127.0.0.1',
'username'=>'root',
'password'=>'root',
'database'=>'think',
'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;
}
/**
* 关闭数据库连接
*/
public function close()
{
$this->mysqli->close();
}
}
2、redis实例化类(redis2.class.php)
<?php
class Redis2
{
private $redis;
public function __construct($host = '127.0.0.1', $port = '6379',$password = '',$db = '15')
{
$this->redis = new Redis();
$this->redis->connect($host, $port); //连接Redis
$this->redis->auth($password); //密码验证
$this->redis->select($db); //选择数据库
}
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);
}
}
3、开启键值写入(index.php)
<?php
/**
* CREATE TABLE `agent`(
* `id` int(10) UNSIGNED NOT NULL,
* `code` varchar(10) NOT NULL,
* `status` tinyint(3) NOT NULL,
* `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
* )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='会员列表';
*/
require_once 'redis2.class.php';
$redis = new \Redis2('127.0.0.1','6379','','15');
//require_once 'db.class.php';
//$order_sn = 'SN'.time().'T'.rand(10000000,99999999);
//$mysql = new \mysql();
//$mysql->connect();
//$data = ['code'=>$order_sn,'status'=>0,'createtime'=>date('Y-m-d H:i:s',time())];
//$mysql->insert('agent',$data);
$list = ['123456', 1];
$key = implode(':',$list);
$redis->setex($key,3,'redis延迟任务'); //3秒后回调
print_r($list);
/*
* 测试其他key会不会有回调,结果:有回调
* $k = 'test';
* $redis2->set($k,'100');
* $redis2->expire($k,10);
*
*/
4、过期事件的订阅(psubscribe.php)
<?php
// Linux环境需要加上下面一行代码,windows环境自主删除即可
#! /usr/bin/env php
ini_set('default_socket_timeout', -1); //不超时
require_once 'redis2.class.php';
$redis_db = '15';
$redis = new \Redis2('127.0.0.1','6379','',$redis_db);
// 解决Redis客户端订阅时候超时情况
$redis->setOption();
//当key过期的时候就看到通知,订阅的key __keyevent@<db>__:expired 这个格式是固定的,db代表的是数据库的编号,由于订阅开启之后这个库的所有key过期时间都会被推送过来,所以最好单独使用一个数据库来进行隔离
$redis->psubscribe(array('__keyevent@'.$redis_db.'__:expired'), 'keyCallback');
// 回调函数,这里写处理逻辑
function keyCallback($redis, $pattern, $channel, $msg)
{
echo PHP_EOL;
echo "Pattern: $pattern\n";
echo "Channel: $channel\n";
echo "Payload: $msg\n\n";
$list = explode(':',$msg);
$code = isset($list[0])?$list[0]:'0';
$use_mysql = isset($list[1])?$list[1]:'0';
if($use_mysql == 1){
require_once 'db.class.php';
$mysql = new \mysql();
$mysql->connect();
$where = "code = '".$code."'";
$mysql->select('agent','',$where);
$finds=$mysql->fetchAll();
print_r($finds);
if(isset($finds[0]['status']) && $finds[0]['status']==0){
$data = array('status' => 3);
$where = " id = ".$finds[0]['id'];
$mysql->update('agent',$data,$where);
}
}
}
//或者
/*$redis->psubscribe(array('__keyevent@'.$redis_db.'__:expired'), function ($redis, $pattern, $channel, $msg){
echo PHP_EOL;
echo "Pattern: $pattern\n";
echo "Channel: $channel\n";
echo "Payload: $msg\n\n";
//................
});*/
结果
后台监听运行(linux)
1、命令
nohup php psubscribe.php > /dev/null 2>&1 &
2、查看进程
jobs -l
3、杀死进程命令
kill -9 PID