目录
1.应用场景
设备于平台之间有心跳,会每2分钟上报一次心跳数据,这样平台就能感知到设备在线。但是如果设备离线,就不会给平台发送心跳,这时,如何判断设备离线?
之前的一种解决方案:每2分钟跑一次定时器,判断数据库中在线的设备,与最近2分钟上报心跳的设备进行比较,如果数据库中是在线但是近2分钟没有收到心跳,说明设备离线。
缺点就是每2分钟需要跑定时器,而且需要存储最近2分钟的所有有心跳上报设备。
优点就是不需要借助第三方中间件,就是跑定时器。
那考虑到redis 的这一功能:能够开启监听key是否过期,过期时可回调通知进行业务处理。那这里的一个解决方案就是:在收到设备心跳时,将设备编号作为key存入redis,有效期是2分钟,如果之后在没到期之前又收到的此设备的心跳,就将有效期再延迟2分钟;如果之后没有收到心跳,在2分钟到的时候,就会触发key过期监听,进行业务处理,将设备置为离线。
缺点是每2分钟需要去更新key的有效期(不知道是否算作缺点)
优点是不需要跑定时器,借助第三方redis中间件的功能就可以解决
其他的很多场景,比如订票只有30分钟有效期,或者需要延迟执行的任务都可以采用这种解决方案
2.redis配置文件
查看Redis的配置文件的 event notification 部分
############################# EVENT NOTIFICATION ##############################
# Redis can notify Pub/Sub clients about events happening in the key space.
# This feature is documented at http://redis.io/topics/notifications
#
# For instance if keyspace events notification is enabled, and a client
# performs a DEL operation on key "foo" stored in the Database 0, two
# messages will be published via Pub/Sub:
#
# PUBLISH __keyspace@0__:foo del
# PUBLISH __keyevent@0__:del foo
#
# It is possible to select the events that Redis will notify among a set
# of classes. Every class is identified by a single character:
#
# 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.
#
# The "notify-keyspace-events" takes as argument a string that is composed
# of zero or multiple characters. The empty string means that notifications
# are disabled.
#
# Example: to enable list and generic events, from the point of view of the
# event name, use:
#
# notify-keyspace-events Elg
#
# Example 2: to get the stream of the expired keys subscribing to channel
# name __keyevent@0__:expired use:
#
# notify-keyspace-events Ex
#
# By default all notifications are disabled because most users don't need
# this feature and the feature has some overhead. Note that if you don't
# specify at least one of K or E, no events will be delivered.
notify-keyspace-events ""
修改配置文件成:
notify-keyspace-events Ex
因为我这里是正常运行中的redis,所以在登录redis-cli后执行:
config set notify-keyspace-events Ex
表示当key过期的时候进行监听
3.命令实现
打开一个redis-cli
订阅PSUBSCRIBE __keyevent@0__:expired
再打开一个redis-cli
执行:set key value [EX EXPIRES]
再过期时间到的时候,前面监听的客户端就能收到信息了
4.springboot实现
redis配置文件
主要是声明 RedisMessageListenerContainer
然后再配置一个监听器
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
log.info("RedisKeyExpirationListener on message");
String channel = new String(message.getChannel()); // __keyevent@*__:expired
String pa = new String(pattern); // __keyevent@*__:expired
String expiredKey = message.toString();
log.info("监听到过期key:{},{},{}",channel,pa , expiredKey);
}
}
这样启动后,在有key过期的时候,就会进入onMessage回调方法。
5.集群只收到单一节点key过期
上面在redis 单节点的情况下,运行良好,都能收到过期的key。
但是在集群环境下,比如是3主3从的集群,springboot 在配置链接了3个主节点时,在同一时间实际上是只链接了一个node
比如6个节点的端口分别是:7001,7002,7003,7004,7005,7006
前面3个是主节点,后面3个是从节点。如果7001节点过期收到key,但是7002 和7003节点的key过期可能就收不到了
也就是说键空间通知是指定node的(自身),不像常规的pub/sub是广播到所有节点的,所以我们需要连接集群中所有的可用节点去获取键空间通知
换句话说,Redis Cluster集群中key采用的是分片存储,不同的key通过哈希计算放到不同的slot槽中,即可能是不同的node节点,而keyspace notification只在自己所在的node上发布,并没有发布到集群当中,我们客户端订阅监听的时候只监听随机的node(即每次建立连接的node是随机的),那么就有可能有些key过期没有被监听到,这就导致说我们收不到这个过期事件。
即集群本身的pub/sub是节点之间交叉广播的,但是键空间通知只支持本地
也就是说因集群模式点对点之间网络带宽的压力,不考虑将键空间通知加入到集群广播中来,更建议是客户端直接连接节点获取键空间通知,但是有个问题就是需要客户端随时检查集群配置,以获取新加入的master节点
上面的几段都来自第三篇参考文章
6.后记
【参考】:
【redis cluster】redis 集群模式下,key过期收不到的问题处理