摘要:任务的使用场景有很多,触发方式也层出不穷,有定时触发、延时触发及多条件触发等。本文将介绍利用redis来实现任务触发的2种场景。如有错误,欢迎指正。欢迎大家关注我的微信公众号虾米兵法(微信号:sammytalk)。
延时触发的实现
延时任务是指在创建后并不马上执行,而在间隔一段时间后才执行的任务,其应用场景比较多,也有各式各样的实现方式。如果你的系统种恰巧使用了redis,那么比较简单的方式就是利用redis中key的超时回调来实现延时任务。
创建延迟触发任务步骤:
- 定义元数据:定义任务id(需要确保唯一性)以及执行该任务的类路径。
- 创建任务并设置延迟执行的时间:在redis中插入一条记录,其中key为任务id,值为任务id,并设置该key的超时时间为该任务的延迟执行时间。
- 创建任务id和执行类的映射:在redis插入一条记录,其中key为“class”+任务id,值为类路径(com.xx.xx.xx.java)。
创建完任务后,在经过延迟执行时间后,系统会收到key超时的事件(如何收到该事件,我们稍后介绍),通过超时事件的通知我们可以获取到任务id,从而可以获取到执行该任务的类路径,这个时候删除该任务id和类路径的映射,反射调用类中的方法即可(强烈建议定义一个接口,统一类执行的入口方法)。
小知识
该延迟任务的核心大家都能看出来是key的超时回调,那么如何获得redis中key的超时回调事件呢?
在redis通知事件是通过发布/订阅来实现的,每种事件类型对应一个channel,从redis配置文档的提示我们可以看到redis支持以下的事件通知。
K Keyspace events, published with __keyspace@<db>__ prefix.
E Keyevent events, published with __keyevent@<db>__ prefix.
g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$ String commands
l List commands
s Set commands
h Hash commands
z Sorted set commands
x Expired events (events generated every time a key expires)
e Evicted events (events generated when a key is evicted for maxmemory)
A Alias for g$lshzxe, so that the "AKE" string means all the events.
在此文中我们需要获取key的超时事件,那么可以在redis的配置文件中修改配置:
notify-keyspace-events Ex
这告诉redis我们需要监听key事件中的超时事件。当有key超时,就可以通过以下的channel获取到通知,0代表需要监听的db序号。
__keyevent@0__:expired"
事件通知的结构如下:
1) "pmessage"
2) "__keyevent@*__:expired"
3) "__keyevent@0__:expired"
4) "b"
通过该回调可以方便的获取到超时的key的值。
时间+外部变量触发的实现
有时候,只通过延时、定时的方式来触发任务可能不太够,我来举个例子:假设有一个定时更新新用户缓存的任务需要循环的拉取新用户的资料到指定缓存中,那么只用定时任务的话,当用户的注册高峰来临时,就会有大量的新用户缓存无法更新,这个时候有必要设置一个阀值,在指定时间段内用户注册数达到一定值时触发该任务。该怎么做呢?
可以利用redis计数+延迟触发的方式来解决这个问题的。
创建延迟+外部变量触发任务步骤:
- 定义元数据:定义任务id(需要确保唯一性)以及执行该任务的类路径。
- 创建任务并设置延迟执行的时间:使用incr在redis中插入(更新)一条记录,其中key为任务id,并设置该key的超时时间为该任务的延迟执行时间。
- 创建任务id和执行类的映射:在redis插入(更新)一条记录,其中key为“class”+任务id,值为类路径(com.xx.xx.xx.java)。
- 如果incr命令返回的值达到阀值(如果只允许执行一次,那么条件将是等于阀值,大于或者小于阀值均不执行),那么删除对应的key(任务id和“class+任务id”),并执行对应的方法。
后续
延迟+外部变量触发任务可用的场景非常多,还可以用在合并并发流量,缓冲并发带来的任务堆积的问题,具体的场景大家自己思考,本文就到这里,欢迎和我交流。