redis过期监听

我的应用场景:因为业务需求,我们会每10分钟从kafka得到数据开始处理,这时就会存在一种情况,如果kafka数据没有传递过来,我们是不是要通过一种方式知道数据没传递通知我们。在这里我选用的模式是redis提供的观察者模式。

原理:从kafka得到的数据保存到redis中,key的失效时间可以自定义配置(我定义15分钟失效),每次从kafka得到数据都去刷新redis,这样如果kafka每次都传递数据,redis就不会失效,如果不传递数据,redis就会失效,然后通过redis的监听器得到这个失效的redis再进行后续处理(我们这边是进行邮件报警)

1:配置redis的失效监听,需要修改redis.conf配置文件

增加:notify-keyspace-events "Ex"

配置文件中找到notify-keyspace-events,修改成notify-keyspace-events "Ex"

Ex 的解释如下:

2:配置文件修改好后,重新启动redis,我的redis是用docker启动的。重新启动了容器。

3:验证redis失效监听是否好用。

进入redis容器:

docker exec -it redis /bin/sh

运行redis客户端:

redis-cli

运行监听命令:

psubscribe __keyevent@0__:expired

再启动一个redis-cli

创建一个10秒后失效的reids:

setex test 10 test

10秒后,可以看到监听端口可以接收到失效的redis的key.

4:java代码编写,pom.xml引入

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

5:配置redis的config,配置了两种方式,第一种方式是支持@Cacheable创建redis.第二种方式是直接引用redis提供的StringRedisTemplate类来调用redis的set和get方法。

第一种方式的配置文件写法:RedisCacheConfig.java

@Configuration

public class RedisCacheConfig{

@Bean

    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {

        return new RedisCacheManager(

                RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory),

                this.getRedisCacheConfigurationBase(), // 默认策略

                this.getRedisCacheConfigurationMap() // 指定 key 策略

        );

}

private RedisSerializer<String> keySerializer() {

        return new StringRedisSerializer();

}

private RedisSerializer<Object> valueSerializer() {

        return new GenericJackson2JsonRedisSerializer();

}

private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {

        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();

        redisCacheConfigurationMap.put("NoDataCache0", this.getRedisCacheConfigurationWithTtl(60));

        return redisCacheConfigurationMap;

    }

private RedisCacheConfiguration getRedisCacheConfigurationBase() {

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()

                //.entryTtl(this.timeToLive)  --定义到期时间

                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))

                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));

                //.disableCachingNullValues(); //保存的数据不能为null

        return config;

    }

private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()

                .entryTtl(Duration.ofSeconds(seconds))  //定义到期时间

                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))

                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));

                //.disableCachingNullValues(); //保存的数据不能为null

        return config;

    }

}

用RedisCacheManager的构造方法来实现,我定义了一个60秒失效的策略,redis的写法如下:

@Cacheable(value = "NoDataCache0" key="'rc_alarmrule_2_'+#tenantId+'_'+#metricSpecId")

public List<AlarmRuleRedisAllVO> getAlarmRuleAllRedis(long tenantId,long metricSpecId) {

return null;

}

1)value = "NoDataCache0" 是我策略里定义的名称redisCacheConfigurationMap.put("NoDataCache0", this.getRedisCacheConfigurationWithTtl(60));

这个可以定义多个。每个的名称和失效时间配置不一样。

2)这种方式的配置,失效时间只能写在配置文件中或者写死,如果我想按照我表中定义一个失效时间实时的进行变化就做不到了。所以我又用了第二种配置方案。

第二种方式的配置文件写法:RedisCacheConfig.java

@Service

public class RedisService {

    @Autowired

    private StringRedisTemplate redisTemplate;

    /**

    * 永久

    * @param key

    * @param value

    */

    public void set(String key, Object value) {

        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value));

    }

    /**

    * 将 key,value 存放到redis数据库中,设置过期时间单位是秒

    * @param key

    * @param value

    * @param timeout

    */

    public void setBySeconds(String key, Object value, long timeout) {

        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), timeout, TimeUnit.SECONDS);

    }

    /**

    * 将 key,value 存放到redis数据库中,设置过期时间单位是分钟

    * @param key

    * @param value

    */

    public void setByMinutes(String key, Object value, long timeout) {

        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), timeout, TimeUnit.MINUTES);

    }

    /**

    * 将 key,value 存放到redis数据库中,设置过期时间单位是小时

    * @param key

    * @param value

    */

    public void setByHours(String key, Object value, long timeout) {

        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), timeout, TimeUnit.HOURS);

    }

    /**

    * 将 key,value 存放到redis数据库中,设置过期时间单位是天

    * @param key

    * @param value

    */

    public void setByDays(String key, Object value, long timeout) {

        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), timeout, TimeUnit.DAYS);

    }

    /**

    * 删除 key 对应的 value

    * @param key

    */

    public void delete(String key) {

        redisTemplate.delete(key);

    }

    /**

    * 获取与 key 对应的对象

    * @param key

    * @param clazz 目标对象类型

    * @param <T>

    * @return

    */

    public <T> T get(String key, Class<T> clazz) {

        String s = get(key);

        if (s == null) {

            return null;

        }

        return JsonUtil.convertString2Obj(s, clazz);

    }

    /**

    * 获取与 key 对应的对象

    * @param key

    * @param clazz 目标对象类型

    * @param <T>

    * @return

    * @throws IOException

    * @throws JsonMappingException

    * @throws JsonParseException

    */

    public <T> List<T> getList(String key, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException {

        String s = get(key);

        if (s == null) {

            return null;

        }

        return JsonUtil.toList(s,clazz);

    }

    /**

    * 获取 key 对应的字符串

    * @param key

    * @return

    */

    public String get(String key) {

        return redisTemplate.opsForValue().get(key);

    }

    /**

    * 查询 key 对应的过期时间

    * @param key

    * @return

    */

    public String getExpire(String key){

        Long timeout = redisTemplate.getExpire(key,TimeUnit.MILLISECONDS);

        System.out.println(timeout);

        if (timeout < 0)

            return "Has expired!";

        Long milliSecond = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() + timeout;

        return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.ofInstant(

                Instant.ofEpochMilli(milliSecond),ZoneId.systemDefault()));

    }

    /**

    * 判断 key 是否在 redis 数据库中

    * @param key

    * @return

    */

    public boolean exists(final String key) {

        return redisTemplate.hasKey(key);

    }

这种方式可以实时的改变redis的失效时间。

6:配置redis失效的监听config  RedisListenerConfig.java

@Configuration

public 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;

    }

}

我没有配置__keyevent@0__:expired",对某个db进行监听,RedisMessageListenerContainer有个默认的配置是对所有的db进行监听。

7:redis的监听类 RedisKeyExpirationListener.java

@Component

public 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();

        logger.debug("失效的redis是:"+expiredKey);

        String part = "NoDataCache0";

        //如果是NoDataCache0:开头的key,进行处理

        if(expiredKey.startsWith(part)){

                //自己的业务逻辑

        }

}

1)注意:失效的redis只能得到key,是得不到value的,所以业务逻辑如果用到value里的值需要把值写到key中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容