1. 键值过期监听

1.1. 问题分析及解决

  • 问题描述:

线上项目需要监听设备的在线状态,通过设备心跳来实现;两分钟内没心跳 redis 会通知程序设备掉线,设备有心跳后回自动挂回 redis;无心跳时通过监听 redis key过期来处理。今天突然发现设备在线,但是 redis 中 key 没有了,导致设备异常,无法接任务。

  • 问题分析:

排查项目设备掉线、上线日志,发现没有打印设备掉线日志,初步排查是 redis 库丢弃了 key,然后排查 redis 配置问题。

  • 问题处理:

将 redis 数据库配置文件redis.conf 中的 notify-keyspace-events "" 改为 notify-keyspace-events "Ex"。

1.2. 问题拓展学习

1.2.1. Redis key过期策略

通过 EXPIRE key seconds 命令来设置数据的过期时间。返回1表明设置成功,返回0表明key不存在或者不能成功设置过期时间。在key上设置了过期时间后key将在指定的秒数后被自动删除。被指定了过期时间的key在Redis中被称为是不稳定的。

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。

1.2.2. Redis key过期的方式有三种

  • 惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key(无法保证冷数据被及时删掉);
  • 定期删除:Redis会定期主动淘汰一批已过期的key(随机抽取一批key检查);
  • 内存淘汰机制:当前已用内存超过maxmemory限定时,触发主动清理策略;

惰性删除

Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

  • 从过期字典中随机 20 个 key;
  • 删除这 20 个 key 中已经过期的 key;
  • 如果过期的 key 比率超过 1/4,那就重复步骤 1;

同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

如果某一时刻,有大量key同时过期,Redis 会持续扫描过期字典,造成客户端响应卡顿,因此设置过期时间时,就尽量避免这个问题,在设置过期时间时,可以给过期时间设置一个随机范围,避免同一时刻过期。

  1. 如何配置定期删除执行时间间隔

redis的定时任务默认是10s执行一次,如果要修改这个值,可以在redis.conf中修改hz的值。

redis.conf中,hz默认设为10,提高它的值将会占用更多的cpu,当然相应的redis将会更快的处理同时到期的许多key,以及更精确的去处理超时。 hz的取值范围是1~500,通常不建议超过100,只有在请求延时非常低的情况下可以将值提升到100。

  1. 单线程的redis,如何知道要运行定时任务?

redis是单线程的,线程不但要处理定时任务,还要处理客户端请求,线程不能阻塞在定时任务或处理客户端请求上,那么,redis是如何知道何时该运行定时任务的呢?

Redis 的定时任务会记录在一个称为最小堆的数据结构中。这个堆中,最快要执行的任务排在堆的最上方。在每个循环周期,Redis 都会将最小堆里面已经到点的任务立即进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是接下来处理客户端请求的最大时长,若达到了该时长,则暂时不处理客户端请求而去运行定时任务。

懒惰删除

定时删除策略中,从删除方法来看,必然会导致有key过期了但未从redis中删除的情况。

面对这种情况,redis在操作一个key时,会先判断这个值是否过期,若已过期,则删除该key;若未过期,则进行后续操作。

Redis 内存淘汰机制

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(常用)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

1.2.3. Redis 中 key 过期的事件触发

Redis的key过期的事件触发

过期事件通过Redis的订阅与发布功能(pub/sub)来进行分发。

  1. redis 服务端配置

超时的监听,并不需要自己发布,只有修改配置文件redis.conf中的:notify-keyspace-events Ex,默认为notify-keyspace-events ""。

修改好配置文件后,redis会对设置了expire的数据进行监听,当数据过期时便会将其从redis中删除。

如果服务宕机,那么将接收不到 redis 推送过来的事件也就无法处理 redis 过期后的逻辑。我们可以采用 redis + 定时任务处理,这样可以避免服务宕机,Redis过期事件推送处理的问题,也能够提高系统整体性能(可以去了解一下 redis 的 key 过期策略)。

  1. Java实现redis中key过期事件触发

  2. 创建一个实现类继承KeyExpirationEventMessageListener

package net.chenqiong.redis.utils;

import com.focussend.contacts.service.FissionMarketingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

import javax.annotation.Resource;

/**
 * Created by chenqiong on 2021/3/19.
 */
@Component
class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
    @Resource
    private FissionMarketingService fissionMarketingService;
    private Logger logger = LoggerFactory.getLogger(RedisKeyExpirationListener.class);

    private final Object fanNumUpdate = new Object();
    /**
     * 针对redis数据失效事件,进行数据处理
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
           String expiredKey = message.toString();
                   logger.info("此处根据对应的redis的key去处理业务逻辑");
       }
}
  • 注入RedisMessageListenerContainer

方法一:

<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
    <property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>

方法二:

/**
 * Redis缓存配置类
 */
@Configuration
public class RedisConfigurer extends CachingConfigurerSupport {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}
  1. 测试

向redis中存入一个key和value并设置过期时间,key过期后会触发 onMessage(Message message, byte[] pattern) 方法 redisCacheManagerTool.set("test_renjiao","haha",20);

Copyright © 神都花已开 2021 all right reserved,powered by Gitbook修订时间: 2021-11-01 15:05:12

results matching ""

    No results matching ""