使用场景:1.支付业务中,未支付订单自动关闭 2.缓存过期提醒
一般情况下,对于未支付订单自动关闭的处理中,我们可以使用定时服务来实现,比如每分钟调用一次接口来处理未支付并且已经过期的订单,但是这样的话,一是会比较损耗计算机性能,即使没有订单的时候也会每分钟进行处理,二是订单处理时间的精确度最大延迟会是 59s ,而且也要保证定时服务一直可用
那我们想只针对当有未支付并过期的订单并且低延迟处理要怎么办呢,我们可以通过 redis 的缓存过期机制来进行订阅推送处理
具体实现:
一 .Redis配置
原理:利用 Keyspace Notifications 功能,允许用户订阅 Sub/Pub 频道 ,当 key键 过期的时候触发事件通知,故需要订阅 _keyevent@0_:expired( 通道0表示 db0,可根据 dbindex 选择合适的值)
实现:将配置文件进行修改
取消 notify-keyspace-events Ex 的注释,对 notify-keyspace-events “” 进行注释
(Ex 为配置参数,意思是监听某个key的失效事件,可参考如下表格参数说明,其中,参数中至少要有一个 K 或者 E , 否则的话, 不管其余的参数是什么, 都不会有任何通知被分发)
字符 | 发送通知 |
---|---|
K | 键空间通知,所有通知以 keyspace@ 为前缀,针对Key |
E | 键事件通知,所有通知以 keyevent@ 为前缀,针对event |
g | DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知 |
$ | 字符串命令的通知 |
l | 列表命令的通知 |
s | 集合命令的通知 |
h | 哈希命令的通知 |
z | 有序集合命令的通知 |
x | 过期事件:每当有过期键被删除时发送 |
e | 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送 |
A | 参数 g$lshzxe 的别名,相当于是All |
二.程序实现
redis 数据库初始化
private static RedisHelper redisHelper1= new RedisHelper(0); private static RedisHelper redisHelper2= new RedisHelper(1);
添加有过期时间的数据
public string AddData() { try { for (int i = 1; i <= 5; i++) { //在 redis db0 中添加五条数据,过期时间为10s redisHelper1.StringSet("order_" + i, i, TimeSpan.FromSeconds(10)); } return "ok"; } catch (Exception e) { return e.Message; } }
过期数据通知处理
public static void OrderData() { try { //监听过期Key键,固定值。 const string ChannelName = "__keyevent@0__:expired"; //进行对 redis db0 订阅,并获取 Key 的值,但是无法监听到 Key 所对应的 Value 值,所以需要将必要数据作为 key 进行存入。 redisHelper1.Subscribe(ChannelName, (channel, key) => { //将 Key 值写入 redis db1 中作为生效依据 redisHelper2.StringSet(key, ""); }); } catch (Exception) { throw; } }
为了保证 OrderData 方法在web程序启动的时候就进行处理,我们将该方法在 Global.asax 进行调用
最后,我们调用 AddData 接口看看效果
数据失效前效果:
数据失效后效果:
不足:
Redis pub/sub 是一种并不可靠的消息机制,他不会做信息的存储,只是在线转发,肯定也没有 ack 确认机制,另外只有订阅段监听才会转发,所以 Keyspace Notifications 也是不可靠的通知系统,如果我们的业务是需要很好的可靠性,那么这还重方式就不是最好的选择。一般我们更加推荐 RabbitMQ 的 DLX(Dead-Letter-Exchange) 来实现,也就是延迟队列功能。只不过 redis 的这种方案更加容易实现,操作成本较低。对于可靠性要求不高的业务还是很方便。