视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
redis是怎么监控失效的key
2020-11-09 06:58:38 责编:小采
文档


先来看一个问题:

如何处理订单过期自动取消,比如下单30分钟未支付自动更改订单状态?

解决方案:

可以利用redis天然的key自动过期机制,下单时将订单id写入redis,过期时间30分钟,30分钟后检查订单状态,如果未支付,则进行处理但是key过期了redis有通知吗?答案是肯定的。

开启redis key过期提醒

修改redis相关事件配置。找到redis配置文件redis.conf,查看“notify-keyspace-events”的配置项,如果没有,添加“notify-keyspace-events Ex”,如果有值,添加Ex,相关参数说明如下:

K:keyspace事件,事件以__keyspace@<db>__为前缀进行发布; 
E:keyevent事件,事件以__keyevent@<db>__为前缀进行发布; 
g:一般性的,非特定类型的命令,比如del,expire,rename等; 
$:字符串特定命令; 
l:列表特定命令; 
s:集合特定命令; 
h:哈希特定命令; 
z:有序集合特定命令; 
x:过期事件,当某个键过期并删除时会产生该事件; 
e:驱逐事件,当某个键因maxmemore策略而被删除时,产生该事件; 
A:g$lshzxe的别名,因此”AKE”意味着所有事件。

redis测试:

打开一个redis-cli ,监控db0的key过期事件

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 ,发送定时过期key

127.0.0.1:6379> setex test_key 3 test_value

观察上一个redis-cli ,会发现收到了过期的keytest_key,但是无法收到过期的value test_value

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) "test_key"

在springboot中使用

pom 中添加依赖

<!-- redis -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

定义配置RedisListenerConfig

import edu.zut.ding.listener.RedisExpiredListener;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.listener.PatternTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;/**
 * @Author lsm
 * @Date 2018/10/27 20:56
 */@Configurationpublic class RedisListenerConfig { @Bean
 RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

 RedisMessageListenerContainer container = new RedisMessageListenerContainer();
 container.setConnectionFactory(connectionFactory);// container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired"));
 return container;
 }
}

定义,实现KeyExpirationEventMessageListener接口,查看源码发现,该接口监听所有db的过期事件keyevent@*:expired"

import edu.zut.ding.constants.SystemConstant;import edu.zut.ding.enums.OrderState;import edu.zut.ding.service.OrderService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.connection.Message;import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;import org.springframework.data.redis.listener.RedisMessageListenerContainer;import org.springframework.stereotype.Component;/**
 * 监听所有db的过期事件__keyevent@*__:expired"
 * @author lsm
 */@Componentpublic class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer);
 } /**
 * 针对redis数据失效事件,进行数据处理
 * @param message
 * @param pattern
 */
 @Override
 public void onMessage(Message message, byte[] pattern) { // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
 String expiredKey = message.toString(); if(expiredKey.startsWith("Order:")){ //如果是Order:开头的key,进行处理
 }
 }
}

或者打开RedisListenerConfig中 container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired")); 注释,再定义,监控__keyevent@0__:expired事件,即db0过期事件。这个地方定义的比较灵活,可以自己定义监控什么事件。

import org.springframework.data.redis.connection.Message;import org.springframework.data.redis.connection.MessageListener;/**
 * @author lsm
 */public class RedisExpiredListener implements MessageListener { /**
 * 客户端监听订阅的topic,当有消息的时候,会触发该方法;
 * 并不能得到value, 只能得到key。
 * 姑且理解为: redis服务在key失效时(或失效后)通知到java服务某个key失效了, 那么在java中不可能得到这个redis-key对应的redis-value。
 * * 解决方案:
 * 创建copy/shadow key, 例如 set vkey "vergilyn"; 对应copykey: set copykey:vkey "" ex 10;
 * 真正的key是"vkey"(业务中使用), 失效触发key是"copykey:vkey"(其value为空字符为了减少内存空间消耗)。
 * 当"copykey:vkey"触发失效时, 从"vkey"得到失效时的值, 并在逻辑处理完后"del vkey"
 * 
 * 缺陷:
 * 1: 存在多余的key; (copykey/shadowkey)
 * 2: 不严谨, 假设copykey在 12:00:00失效, 通知在12:10:00收到, 这间隔的10min内程序修改了key, 得到的并不是 失效时的value.
 * (第1点影响不大; 第2点貌似redis本身的Pub/Sub就不是严谨的, 失效后还存在value的修改, 应该在设计/逻辑上杜绝)
 * 当"copykey:vkey"触发失效时, 从"vkey"得到失效时的值, 并在逻辑处理完后"del vkey"
 * 
 */
 @Override
 public void onMessage(Message message, byte[] bytes) { byte[] body = message.getBody();// 建议使用: valueSerializer
 byte[] channel = message.getChannel();
 System.out.print("onMessage >> " );
 System.out.println(String.format("channel: %s, body: %s, bytes: %s"
 ,new String(channel), new String(body), new String(bytes)));
 }

}

下载本文
显示全文
专题