出处: 使用Redis完成定时任务

场景

  使用Java做过项目的人大概都用过定时器。一般来说,项目里订单模块和评论模块,都会涉及到定时任务执行。比如说:

  • 用户下订单后,需要在5分钟内完成支付,否则订单关闭;
  • 用户在完成订单后,如果没有评论,过一星期,系统自动评论,并完结。

我以前曾经做过一个租车系统,其中订单的预约逻辑是这样的:

  1. 用户选择车辆并预约
  2. 后台系统开始计时
  3. 计时结束后,如果用户没有进行支付,则取消本次订单

 当时后台计时部分的技术,用的就是java中的定时器类Timer ,使用schedule来设置定时任务。虽然说功能实现了,但还是有很多问题,因为Timer本质上还是启动了一个线程来进行处理。当预约用户过多时,系统内存就会飙升,而且当发布新功能时,如果重启服务器,所有的定时器都会丢失。

解决思路

薛定谔解决法

  在订单信息中,加入过期时间,当用户查询订单或其他操作时,检查一下有没有过期的预订单,如果有,则进入逻辑处理。也就是说,当用户不进行操作时,这个预订单是不会自己结束的。这样做的好处在于,当系统重启时,这个订单的状态是不会收到影响的。坏处当然也显而易见,延迟率太高,主动权完全决定在用户手中。

轮询法

  同样的,在订单信息中加入过期时间,后台启动一个定时线程,每隔一段时间遍历一次订单信息,如果有到期的,则结束订单。这种方法同样会影响系统性能。

使用Redis定时器解决

Redis定时器

  Redis中有一个expire命令,用来设置key的过期时间。使用发布订阅,可以接收到key的过期提醒,当key过期时,再执行取消订单的逻辑,就可以了。

redis 定时器演示

1
2
3
4
127.0.0.1:6379> set test tom EX 10
OK
127.0.0.1:6379> get test
(nil)

设置test(key)的过期时间为10秒,10秒过后key自动销毁。

当然,仅仅有定时器还是不够的,接下来看redis的另一个功能,发布订阅。

Redis发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 客户端可以订阅任意数量的频道。

用一张图来展示频道(channel1)与订阅者(client2, client5, client1)的关系:

当有消息发布时,他们的关系:

消息经由频道广播到每个订阅者中。

发布订阅演示:

首先创建一个频道:

1
2
3
4
5
127.0.0.1:6379> SUBSCRIBE chat1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "chat1"
3) (integer) 1

此时控制台进入阻塞状态。

开启2个控制台,分别订阅chat1频道

1
2
3
4
5
127.0.0.1:6379> PSUBSCRIBE chat1
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "chat1"
3) (integer) 1

这两个控制台也依次进入阻塞状态。

再开一个控制台,进行信息的发布:

1
2
3
127.0.0.1:6379> PUBLISH chat1 "hello everyone!"
(integer) 3
127.0.0.1:6379>

此时两个订阅者和一个频道创建者都分别收到了相同的内容 hello everyone!

这里放上一张图片,效果可能会比较好:

发消息之前

发消息之后

redis的key过期通知也是基于发布订阅模型的。不同的是订阅频道是固定的__keyevent@0__:expired,当然,redis还有好多类似与这种特定频道的通知,想了解更多,可以看这里Redis键空间通知

Redis过期通知

要使用Redis的过期通知功能,需要首先开启该功能 (2.8.0及以上的版本才有此功能)。

在配置文件中加入如下语句:

1
notify-keyspace-events Ex

控制台1订阅频道__keyevent@0__:expired

1
2
3
4
5
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

控制台2存入一个key,并设置过期时间

1
2
127.0.0.1:6379> set test tom EX 10
OK

当10秒过后,控制台1收到信息

1
2
3
4
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "test"

使用springboot接收Redis过期通知

首先在maven中配置redis
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

这个包是spring实现的redis client功能的包。

在springboot配置文件中配置redis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#Redis配置
#数据库索引,默认为0
spring.redis.database=0
#服务器地址,默认localhost
spring.redis.host=localhost
#端口,默认6379
spring.redis.port=6379
#密码,默认为空
spring.redis.password=
#连接池最大连接数,默认为8
spring.redis.jedis.pool.max-active=8
#连接池最大阻塞等待时间,使用负值表示没有限制
spring.redis.jedis.pool.max-wait=-1
#连接池最大空闲连接,默认为8
spring.redis.jedis.pool.max-idle=8
#连接池中的最小空闲连接,默认为0
spring.redis.jedis.pool.min-idle=0
#连接超时时间(毫秒)
spring.redis.timeout=10
代码实现

首先写接收通知的处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class RedisMessageReceiver { /**
* 接收redis消息,并处理
*
* @param message 过期的redis key
*/
public void receiveMessage(String message) {
System.out.println("通知的key是:" + message);
} }

再写频道订阅的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class RedisConfig { /**
* redis 订阅频道
*
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅通道,key过期时通知
container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@0__:expired"));
// 可以订阅多个通道 return container;
} /**
* 配置redis事件监听处理器
*
* @param receiver
* @return
*/
@Bean
MessageListenerAdapter listenerAdapter(RedisMessageReceiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
} }

这样整个代码就完成了。我们来测试一下效果:

使用控制台,新增一个key,并设置过期时间为10秒

1
2
3
127.0.0.1:6379> set testnofity xxx EX 10
OK
127.0.0.1:6379>

切换到我们的程序中,可以在控制台看到如下信息:

  好了,现在我们就可以根据不同的key做不同的业务逻辑处理了。比如规定,所有订单的key,都必须以order-订单号的形式存入,这样,当接收到订单过期的通知时,就可以解析出订单信息,进一步处理了。

  当然,这个只是一种解决思路,你也可以使用一些其他的方式实现,比如说使用RabbitMQ消息队列实现。

Redis 键空间事件通知的更多相关文章

  1. python中的Redis键空间通知(过期回调)

    介绍 Redis是一个内存数据结构存储库,用于缓存,高速数据摄取,处理消息队列,分布式锁定等等. 使用Redis优于其他内存存储的优点是Redis提供持久性和数据结构,如列表,集合,有序集和散列. 在 ...

  2. Redis键空间通知(keyspace notification),事件订阅

      Redis键空间通知(keyspace notification),事件订阅   应用场景:有效期优惠券.24小时内支付.下单有效事件等等. 功能概览 键空间通知使得客户端可以通过订阅频道或模式, ...

  3. Redis 键空间通知

    [Redis 键空间通知] 键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 Redis 数据集的事件. 以下是一些键空间通知发送的事件的例子: 所有修改键的命令. 所有接收到 ...

  4. redis键空间通知(keyspace notification)

    一.需求 在redis中,设置好key和生存时间之后,希望key过期被删除时能够及时的发送一个通知告诉我key,以便我做后续的一些操作. 二.环境 系统:windows10 php:7.1 redis ...

  5. Redis源码解析:09redis数据库实现(键值对操作、键超时功能、键空间通知)

    本章对Redis服务器的数据库实现进行介绍,说明Redis数据库相关操作的实现,包括数据库中键值对的添加.删除.查看.更新等操作的实现:客户端切换数据库的实现:键超时相关功能的实现.键空间事件通知等. ...

  6. 利用Redis keyspace notification(键空间通知)实现过期提醒

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

  7. 10Redis键空间通知(keyspace notifications)

    Redis的键空间通知(keyspace notifications)功能是自2.8.0版本开始加入的,客户端可以通过订阅/发布(Pub/Sub)机制,接收那些以某种方式改变了Redis数据空间的事件 ...

  8. springboot使用Redis,监听Redis键过期的事件设置与使用代码

    我使用的是Windows下的Redis服务,所以一下Redis设置都是在Windows平台进行. 1.修改Redis配置文件 1.1:Windows下的Redis存在两个配置文件 修改带有servic ...

  9. Redis事件通知示例

    1. redis如同zk一样,提供了事件监听(或者说是回调机制), 下面是redis的配置说明: ############################# EVENT NOTIFICATION ## ...

随机推荐

  1. HDU 2176 取(m堆)石子游戏 —— (Nim博弈)

    如果yes的话要输出所有情况,一开始觉得挺难,想了一下也没什么. 每堆的个数^一下,答案不是0就是先取者必胜,那么对必胜态显然至少存在一种可能性使得当前局势变成必败的.只要任意选取一堆,把这堆的数目变 ...

  2. 判定Java程序员等级,HashMap就够了

    JDK1.8  HashMap源码分析 用到的符号: ^异运算:两个操作数相同,结果是;两个操作数不同,结果是1. &按位与:两个操作数都是1,结果才是1. 一.HashMap概述 在JDK1 ...

  3. Mybatis传递多个参数进行SQL查询的用法

    当只向xxxMapper.xml文件中传递一个参数时,可以简单的用“_parameter”来接收xxxMapper.java传递进来的参数,并代入查询. 但是,如果在xxxMapper.java文件中 ...

  4. FreeMarker学习(内建函数参考)

    内容参考:http://freemarker.foofun.cn/dgui_quickstart_basics.html 一.字符串内建函数 boolean: 字符串转为布尔值.字符串必须是 true ...

  5. react 实现数据双向绑定

    好久没有更新了 只是都写在有道笔记中 今天整理下 一些基础的 大神勿喷 一个基础的不能再基础的数据双向绑定 因为react不同于vue 没有v-model指令 所以怎么实现呢? import Reac ...

  6. 图解Python 【第六篇】:面向对象-类-进阶篇

    由于类的内容比较多,分为类-初级基础篇和类-进阶篇 本节内容一览图: 一.类成员修饰符 每一个类的成员都有两种形式: 公有成员,在任何地方都能访问 私有成员,只能在类的内部才能访问 1.1.私有成员和 ...

  7. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-10.Springboot2.x用户登录拦截器开发实战

    笔记 10.Springboot2.x用户登录拦截器开发实战     简介:实战开发用户登录拦截器拦截器 LoginInterceptor                  1.实现接口 LoginI ...

  8. Delphi 10.2.3 精简版自动激活Embarcadero Delphi 10.2.3 v25.0.29899.2631 Lite v14.4

    下载:https://maxwoods.ctfile.com/u/758954/28516301 Embarcadero.Delphi.10.2.RTM.v25.0.26309.314.Lite.v1 ...

  9. Linux shell脚本重试机制

    重试机制在实际编程场景中应用比较场景,比如你的任务在请求一个正在写入数据但不确定什么时间会完成的文件,可能就需要通过尝试机制间隔一段时间重新执行任务. 以下 shell 脚本是实现重试机制的模板: # ...

  10. SQL Server 中 ROWLOCK 行级锁

    一.ROWLOCK的使用 1.ROWLOCK行级锁确保,在用户取得被更新的行,到该行进行更新,这段时间内不被其它用户所修改.因而行级锁即可保证数据的一致性,又能提高数据操作的并发性. 2.ROWLOC ...