learn-tech/专栏/Redis核心原理与实战/31实战:定时任务案例.md
2024-10-16 06:37:41 +08:00

171 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
31 实战:定时任务案例
我在开发的时候曾经遇到了这样一个问题,产品要求给每个在线预约看病的患者,距离预约时间的前一天发送一条提醒推送,以防止患者错过看病的时间。这个时候就要求我们给每个人设置一个定时任务,用前面文章说的延迟队列也可以实现,但延迟队列的实现方式需要开启一个无限循环任务,那有没有其他的实现方式呢?
答案是肯定的,接下来我们就用 Keyspace Notifications键空间通知来实现定时任务定时任务指的是指定一个时间来执行某个任务就叫做定时任务。
开启键空间通知
默认情况下 Redis 服务器端是不开启键空间通知的,需要我们手动开启。
键空间开启分为两种方式:
命令设置方式
配置文件设置方式
接下来,我们分别来看。
命令设置方式
使用 redis-cli 连接到服务器端之后,输入 config set notify-keyspace-events Ex 命令可以直接开启键空间通知功能返回“OK”则表示开启成功如下命令所示
127.0.0.1:6379> config set notify-keyspace-events Ex
OK
优点:
设置方便,无序启动 Redis 服务。
缺点:
这种方式设置的配置信息是存储在内存中的,重启 Redis 服务之后,配置项会丢失。
配置文件设置方式
找到 Redis 的配置文件 redis.conf设置配置项 notify-keyspace-events Ex然后重启 Redis 服务器。
优点:
无论 Redis 服务器重启多少次,配置都不会丢失。
缺点:
需要重启 Redis 服务。
配置说明
可以看出无论是那种方式,都是设置 notify-keyspace-events Ex其中 Ex 表示开启键事件通知里面的 key 过期事件。
更多配置项说明如下:
K键空间通知所有通知以 __keyspace@<db>__ 为前缀
E键事件通知所有通知以 __keyevent@<db>__ 为前缀
gDEL、EXPIRE、RENAME 等类型无关的通用命令的通知
$:字符串命令的通知
l列表命令的通知
s集合命令的通知
h哈希命令的通知
z有序集合命令的通知
x过期事件每当有过期键被删除时发送
e驱逐evict事件每当有键因为 maxmemory 政策而被删除时发送
A参数 g$lshzxe 的别名
以上配置项可以自由组合,例如我们订阅列表事件就是 El但需要注意的是如果 notify-keyspace-event 的值设置为空,则表示不开启任何通知,有值则表示开启通知。
功能实现
我们要实现定时任务需要使用 Pub/Sub 订阅者和发布者的功能,使用订阅者订阅元素的过期事件,然后再执行固定的任务,这就是定时任务的实现思路。
以本文开头的问题为例,我们是这样实现此定时任务的,首先根据每个患者预约的时间往前推一天,然后再计算出当前时间和目标时间(预约前一天的时间)的毫秒值,把这个值作为元素的过期时间设置到 Redis 中,当这个键过期的时候,我们使用订阅者模式就可以订阅到此信息,然后再发提醒消息给此用户,这样就实现了给每个患者开启一个单独的分布式定时任务的功能。
我们先用命令的模式来模拟一下此功能的实现,首先,我们使用 redis-cli 开启一个客户端,监听 __keyevent@0__:expired 键过期事件,此监听值 __keyevent@0__:expired 为固定的写法,其中 0 表示第一个数据库,我们知道 Redis 中一共有 16 个数据,默认使用的是第 0 个,我们建议新开一个非 0 的数据库专门用来实现定时任务,这样就可以避免很多无效的事件监听。
命令监听如下:
127.0.0.1:6379> psubscribe __keyevent@0__:expired
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
此时我们开启另一个客户端,添加两条测试数据试试,命令如下:
127.0.0.1:6379> set key value ex 3
OK
127.0.0.1:6379> set user xiaoming ex 3
OK
等过去 3 秒钟之后,我们去看监听结果如下:
127.0.0.1:6379> psubscribe __keyevent@0__:expired
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "key" #接收到过期信息 key
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "user" #接收到过期信息 user
已经成功的介绍到两条过期信息了。
代码实战
本文我们使用 Jedis 来实现定时任务,代码如下:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;
/**
* 定时任务
*/
public class TaskExample {
public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称
public static void main(String[] args) {
Jedis jedis = JedisUtils.getJedis();
// 执行定时任务
doTask(jedis);
}
/**
* 订阅过期消息,执行定时任务
* @param jedis Redis 客户端
*/
public static void doTask(Jedis jedis) {
// 订阅过期消息
jedis.psubscribe(new JedisPubSub() {
@Override
public void onPMessage(String pattern, String channel, String message) {
// 接收到消息,执行定时任务
System.out.println("收到消息:" + message);
}
}, _TOPIC);
}
}
小结
本文我们通过开启 Keyspace Notifications 和 Pub/Sub 消息订阅的方式,可以拿到每个键值过期的事件,我们利用这个机制实现了给每个人开启一个定时任务的功能,过期事件中我们可以获取到过期键的 key 值,在 key 值中我们可以存储每个用户的 id例如“user_1001”的方式其中数字部分表示用户的编号通过此编号就可以完成给对应人发送消息通知的功能。