Redis 键空间通知 Keyspace Notification 事件订阅

320 阅读5分钟

概述

本文所说的定时任务或者说计划任务并不是很多人想象中的那样,比如说每天凌晨三点自动运行起来跑一个脚本。这种都已经烂大街了,随便一个 Crontab 就能搞定了。

这里所说的定时任务可以说是计时器任务,比如说用户触发了某个动作,那么从这个点开始过二十四小时我们要对这个动作做点什么。那么如果有 1000 个用户触发了这个动作,就会有 1000 个定时任务。于是这就不是 Cron 范畴里面的内容了。

举个最简单的例子,一个用户推荐了另一个用户,我们定一个二十四小时之后的任务,看看被推荐的用户有没有来注册,如果没注册就给他搞一条短信过去。

功能概览

键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 Redis 数据集的事件。事件通过 Redis 的订阅与发布功能(pub/sub)来进行分发, 因此所有支持订阅与发布功能的客户端都可以在无须做任何修改的情况下, 直接使用键空间通知功能。

键空间消息

在 Redis 的 2.8.0 版本之后,其推出了一个新的特性——键空间消息(Redis Keyspace Notifications),它配合 2.0.0 版本之后的 SUBSCRIBE 就能完成这个定时任务的操作了,不过定时的单位是秒。

Publish / Subscribe

Redis 在 2.0.0 之后推出了 Pub / Sub 的指令,大致就是说一边给 Redis 的特定频道发送消息,另一边从 Redis 的特定频道取值——形成了一个简易的消息队列。

Redis Keyspace Notifications

在 Redis 里面有一些事件,比如键到期、键被删除等。然后我们可以通过配置一些东西来让 Redis 一旦触发这些事件的时候就往特定的 Channel 推一条消息。

大致的流程就是我们给 Redis 的某一个 db 设置过期事件,使其键一旦过期就会往特定频道推消息,我在自己的客户端这边就一直消费这个频道就好了。

以后一来一条定时任务,我们就把这个任务状态压缩成一个键,并且过期时间为距这个任务执行的时间差。那么当键一旦到期,就到了任务该执行的时间,Redis 自然会把过期消息推去,我们的客户端就能接收到了。这样一来就起到了定时任务的作用。

配置

因为开启键空间通知功能需要消耗一些 CPU , 所以在默认配置下, 该功能处于关闭状态。可以通过修改 redis.conf 文件, 或者直接使用 CONFIG SET 命令来开启或关闭键空间通知功能。

  • 当 notify-keyspace-events选项的参数为空字符串时,功能关闭。
  • 当参数不是空字符串时,功能开启。

notify-keyspace-events 的参数可以是以下字符的任意组合, 它指定了服务器该发送哪些类型的通知

图片

输入的参数中至少要有一个 K 或者 E , 否则的话, 不管其余的参数是什么, 都不会有任何通知被分发。举个例子, 如果只想订阅键空间中和列表相关的通知, 那么参数就应该设为 Kl , 诸如此类。

具体配置

首先找到redis.conf配置文件,打开文件,查找notify-keyspace-events,将前面的#去掉即可。

图片

注意:这里配置的是notify-keyspace-eventsEx参数,即说明,当键过期的时候会触发通知,如果只需要哈希命令键触发通知则可以设置为notify-keyspace-events Eh

配置完成重启redis-server即可

使用

命令行

开启一个终端,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,新增一个 20秒过期的键

127.0.0.1:6379> SETEX username 30 Tinywan        
OK
127.0.0.1:6379> get username
"Tinywan"
127.0.0.1:6379> TTL username
(integer) 25
127.0.0.1:6379> 

另外一边执行了阻塞订阅操作后的终端,20秒过期后有如下信息输出:

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
1"pmessage"
2"__keyevent@0__:expired"
3"__keyevent@0__:expired"
4"username"

输出以上信息,说明对过期Key信息的订阅是成功的。

发布订阅截图

图片

PHP语言代码

Redis实例类RedisInstance

<?php
/**
 * @desc RedisInstance.php 描述信息
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/6/26 21:36
 */
declare(strict_types=1);

class RedisInstance
{
    private Redis $redis;

    /**
     * @param string $host
     * @param int $port
     * @throws RedisException
     */
    public function __construct(string $host '127.0.0.1'int $port 6379)
    {
        $this->redis = new Redis();
        $this->redis->connect($host$port);
    }

    /**
     * @desc expire
     * @param null $key
     * @param int $time
     * @return bool|Redis
     * @throws RedisException
     * @author Tinywan(ShaoBo Wan)
     */
    public function expire($key nullint $time 0)
    {
        return $this->redis->expire($key$time);
    }

    /**
     * @desc psubscribe
     * @param $callback
     * @param array $patterns
     * @throws RedisException
     * @author Tinywan(ShaoBo Wan)
     */
    public function psubscribe($callbackarray $patterns = [])
    {
        $this->redis->psubscribe($patterns$callback);
    }

    /**
     * @desc setOption
     * @author Tinywan(ShaoBo Wan)
     */
    public function setOption()
    {
        $this->redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
    }
}

订阅文件psubscribe.php

<?php
/**
 * @desc psubscribe.php 描述信息
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/6/26 21:39
 */

require '../vendor/autoload.php';

$redis new \RedisInstance('dnmp-redis');
$redis->setOption();
$redis->psubscribe(function ($redis$pattern$channel$msg){
    echo 'Pattern:' . $pattern .PHP_EOL;
    echo 'Channel:' . $channel .PHP_EOL;
    echo 'Message:' . $msg .PHP_EOL;
}, ['__keyevent@0__:expired']);

运行psubscribe.php 观察订阅状态

# php psubscribe.php

Pattern:__keyevent@0__:expired
Channel:__keyevent@0__:expired
Message:username

Pattern:__keyevent@0__:expired
Channel:__keyevent@0__:expired
Message:username2

Pattern:__keyevent@0__:expired
Channel:__keyevent@0__:expired
Message:username3

发布事件

127.0.0.1:6379> SETEX username 3 Tinywan
OK
127.0.0.1:6379> SETEX username2 5 Tinywan
OK
127.0.0.1:6379> SETEX username3 5 Tinywan

小结结

通过以上步骤,成功地实现了Redis键空间通知使用。首先配置Redis服务器,开启键空间通知功能,然后通过命令行和编写客户端代码来接收并处理通知。这个功能可以帮助我们实时地获取数据库操作的变化,非常适用于需要实时更新数据的应用程序。