redis過期監聽

weixin_34365417發表於2018-12-28

我的應用場景:因為業務需求,我們會每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"

14796116-33ab6dc554456478.png

Ex 的解釋如下:

14796116-4f5c045ce523d09f.png

2:配置檔案修改好後,重新啟動redis,我的redis是用docker啟動的。重新啟動了容器。

3:驗證redis失效監聽是否好用。

進入redis容器:

docker exec -it redis /bin/sh

執行redis客戶端:

redis-cli

執行監聽命令:

psubscribe __keyevent@0__:expired

14796116-eea26399791ae16f.png

再啟動一個redis-cli

建立一個10秒後失效的reids:

setex test 10 test

14796116-54d54c388f907a7f.png

10秒後,可以看到監聽埠可以接收到失效的redis的key.

14796116-180661736ba2fcbd.png

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中。

相關文章