基于redis超时通知的设计

Redis自带Notifications的局限性

redis自带expire机制,结合Redis Keyspace Notifications通知机制,可以实现对redis中key超时通知(具体实现可参见:SpringBoot实现监听redis key失效事件),但是redis的超时通知机制也是有局限的,并不保证通知消息一定可达。

Redis事件通知,即客户端通过订阅sub的形式来接收到Redis服务端数据发生改变的event通知,
通知类型:keyspace(关注key),keyevent(关注操作),
Redis pub/sub采取的是发送即忘(fire and forget)策略,存在丢失通知的可能性(可靠性差,断线、离线、网络不稳定均会丢失消息);
同时Redis事件通知无法在多实例间负载均衡(即所有实例都会收到同一条Redis通知,需要应用做去重(幂等)、分片处理等),
关于Redis key超时通知,官网说明无法严格保证时效性(存在显著延时,适用于超时的key被频繁访问);

故若想在生产环境保证超时通知一定可达,必须还得借助其他补偿机制(例如可结合定时调度),综上最终放弃了是使用redis自带的通知机制来实现超时通知;

常见的超时通知实现

同时关于超时通知,实现的方式基本可以概括如下:
(1)(分布式)定时调度扫描DB表(DB表最好单独新建worker表,以免阻塞主业务表,扫描出满足条件(已超时)的数据);
(2) 使用Redis中的Sorted Set,将score设置为超时到期的时间点,之后定时扫描zSet中score小于当前时间的member即为已超时的数据,同时在处理数据前手动删除member,只有当删除member成功时才去处理数据,否则略过数据(此删除机制可保证超时的member只能被一个实例处理,可实现类似分片调度的机制,且处理具有一定随机性);
(3) 使用mq的延时通知机制,例如RabbitMq中的Dead Letter转发机制(RabbitMq中自带的Dead Letter机制具有延时(即在超期之后的再一段时间才收到通知)的可能,最好通过安装插件rabbitmq_delayed_message_exchange来实现延时队列功能,参考:RabbitMQ延迟队列);
(4) 其他单实例(由或者app端自应用的超时通知)可结合定时调度框架(Timer、quartz、DelayQueue等)实现;

结合Redis zSet的定时调度超时通知实现

由于业务已经接入了Redis,又不想再格外去引入其他框架、中间件(如RabbitMq等),故最终选用了Redis zSet的方式;

相关定义如下:
zSet名称:expire_collections_key
member名称:userId=232
score: 超时时间点的时间戳expireTimestamp(ms,长整形)

基本操作如下:
(1)设置超时

zadd {key} {score} {member}
zadd {key} {expireTimestamp} {userId}
zadd expire_collections_key 1587709741000 232

(2)判断是否超时

zscore {key} {member}
zscore {key} {userId}
zscore expire_collections_key 232

获取指定userId对应的score(超时时间戳),若score小于当前时间则表示已超期;

(3)超时通知
定时扫描zSet中所有score小于当前时间戳的member(userId)即为已超期的数据;
curTimeStamp = new Date().getTime() = 1587710221000

zrange {key} {startScore} {stopScore}
zrange {key} 0 {curTimestamp}
zrange expire_collections_key 0 1587710221000

同时在处理具体的member(userId)之前,需要手动删除该member,只有当删除member成功时才去处理数据,否则略过数据;

zrem {key} {member}
zrem expire_collections_key 232
发布了59 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/luo15242208310/article/details/105727678