一種消息系統.spring boot redis失效key監聽

springboot,idea,jdk8

遇到一個需求,需要每週一向用戶推送通知,用戶參與的視頻會議開始前60min,15min,5min給其發送通知.用戶關注會議開始前10min推送,還有其他兩種通知.用戶在線,即時收到;用戶不在線,登錄時收到.

首先需要配置 websocket  ,網上有很多相關資料,這個我就不贅述了.接下來就要完成相關需求了.

第一反應是使用 定時器 ,只要在啓動類上加

然後創建文件

就可解決.???發現自己想的有點多,因爲通知類型多種,如果創建多個定時器感覺很繁瑣,況且通知時間不固定,定時器就很難滿足需求.( java.util 包下的 Timer類 也有類似定時功能).週期性的,時間固定的任務更適合使用定時器.比如需求中的每週一早上8點發送廣播通知,這種比較適合使用定時器.


@Scheduled() 內的參數問題:

  @Scheduled(fixedRate = default -1L ),既 fixedRate = 2000,每兩秒執行一次.

  @Scheduled(cron = default ""),corn的值可以自定義,其值有6個,使用空格隔開,按順序依次爲

      秒(0~59)
      分鐘(0~59)
      小時(0~23)
      天(0~31)
      月(0~11)
      星期(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
      年份(1970-2099)

  其中年可以省略. 每個元素可以是一個值(如6),一個連續區間(9-12),一個間隔時間(8-18/4)(/表示每隔4小時),一個列表(1,3,5),通配符。由於"月份中的日期"和"星期中的日期"這兩個元素互斥的,必須要對其中一個設置 ? .

  例如 corn = "0 0 8 ? * 6L"  表示每月的最後一個星期五上午8:00觸發 .

有些表達式能包含一些範圍或列表
       例如:子表達式(天(星期))可以爲 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”
       “*”字符代表所有可能的值
       “/”字符用來指定數值的增量
       例如:在子表達式(分鐘)裏的“0/15”表示從第0分鐘開始,每15分鐘
                在子表達式(分鐘)裏的“3/20”表示從第3分鐘開始,每20分鐘(它和“3,23,43”)的含義一樣
       “?”字符僅被用於天(月)和天(星期)兩個子表達式,表示不指定值
       當2個子表達式其中之一被指定了值以後,爲了避免衝突,需要將另一個子表達式的值設爲“?”
       “L” 字符僅被用於天(月)和天(星期)兩個子表達式,它是單詞“last”的縮寫
       如果在“L”前有具體的內容,它就具有其他的含義了。例如:“6L”表示這個月的倒數第6天
       注意:在使用“L”參數時,不要指定列表或範圍,因爲這會導致問題
       W 字符代表着平日(Mon-Fri),並且僅能用於日域中。它用來指定離指定日的最近的一個平日。大部分的商業處理都是基於工作周的,所以 W 字符可能是非常重要的。
       例如,日域中的 15W 意味着 "離該月15號的最近一個平日。" 假如15號是星期六,那麼 trigger 會在14號(星期五)觸發,因爲星期四比星期一離15號更近。
       C:代表“Calendar”的意思。它的意思是計劃所關聯的日期,如果日期沒有被關聯,則相當於日曆中所有日期。例如5C在日期字段中就相當於日曆5日以後的第一天。1C在星期字段中相當於星期日後的第一天。
       字段   允許值   允許的特殊字符
       秒               0-59           , - * /
       分               0-59           , - * /
       小時           0-23           , - * /
       日期           1-31           , - * ? / L W C
       月份           1-12 或者 JAN-DEC           , - * /
       星期           1-7 或者 SUN-SAT           , - * ? / L C #
       年(可選)           留空, 1970-2099           , - * /


需求並未解決,上網查找,發現使用redis過期key監聽可以很好地解決問題.訂單過期自動取消,比如下單30分鐘未支付自動更改訂單狀態這類問題也可以使用此方法.

pom文件中需要相關依賴

第一步:

首先在 redis安裝包下 redis.conf 配置文件裏面找到 #  notify-keyspace-events Ex 打開註釋,然後重啓redis.一定要修改,不然接下來的配置無效,啓動項目會報錯.

第二步:

添加redis監聽方法

/**
 * redis過期回調函數
 */
@Component
public class KeyExpiredListener extends KeyExpirationEventMessageListener {

    public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // message.toString()可以返回redis庫中失效的 key
        // your code
    }
}

第三步:

添加redis監聽配置

@Configuration
public class RedisConfiguration extends RedisMessageListenerContainer {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public ChannelTopic expiredTopic() {
        return new ChannelTopic("__keyevent@0__:expired");  // 選擇0號數據庫
    }

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer() {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory);
        return redisMessageListenerContainer;
    }

    @Bean
    public KeyExpiredListener keyExpiredListener() {
        return new KeyExpiredListener(this.redisMessageListenerContainer());
    }

就可以簡單實現redis過期key的監聽了.

這種方式需要部署的項目服務器上安裝 redis ,不是很合適.而且把不同類型消息存入不同庫中比較難做到.所以需要進一步的配置.舉一個栗子:

首先是 application.yml 中 redis 的相關配置

 

接下來配置 redis-a 失效 key 的回調函數

public class KeyExpiredListenerA extends KeyExpirationEventMessageListener {

    public KeyExpiredListenerA(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        System.out.println(message.toString() + "過期了........................................");
    }
}

 

再配置redis-b 失效key的回調函數

public class KeyExpiredListenerB extends KeyExpirationEventMessageListener {
    public KeyExpiredListenerB(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        System.out.println(message.toString() + "過期了........................................");
    }
}

 

然後配置 redis 配置文件

@Configuration
public class RedisConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
    public GenericObjectPoolConfig redisPool() {
        return new GenericObjectPoolConfig();
    }

    // a redis配置信息
    @Bean
    @ConfigurationProperties(prefix = "spring.redis.redis-a")
    public RedisStandaloneConfiguration redisConfigA() {
        return new RedisStandaloneConfiguration();
    }

    // a redis連接
    @Bean
    @Primary
    public JedisConnectionFactory getJedisConnectionFactoryA() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisConfigA());
        return jedisConnectionFactory;
    }

    // a redis監聽
    @Bean
    @Primary
    public RedisMessageListenerContainer redisMessageListenerContainerA() {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(getJedisConnectionFactoryA());
        return redisMessageListenerContainer;
    }

    // a redis監聽回調函數注入
    @Bean
    public KeyExpiredListenerA redisTimerListenerA() {
        KeyExpiredListenerA KeyExpiredListenerA = new KeyExpiredListenerA(redisMessageListenerContainerA());
        return KeyExpiredListenerA;
    }

    // b redis配置信息
    @Bean
    @ConfigurationProperties(prefix = "spring.redis.redis-b")
    public RedisStandaloneConfiguration redisConfigB() {
        return new RedisStandaloneConfiguration();
    }

    // b redis連接
    @Bean
    public JedisConnectionFactory getJedisConnectionFactoryB() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisConfigB());
        return jedisConnectionFactory;
    }

    // b redis監聽
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainerB() {
        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        redisMessageListenerContainer.setConnectionFactory(getJedisConnectionFactoryB());
        return redisMessageListenerContainer;
    }

    // b redis監聽回調函數注入
    @Bean
    public KeyExpiredListenerB redisTimerListenerB() {
        KeyExpiredListenerB KeyExpiredListenerB = new KeyExpiredListenerB(redisMessageListenerContainerB());
        return KeyExpiredListenerB;
    }

    // a 連接 RedisTemplate 配置
    @Bean
    public RedisTemplate<String, Object> redisTemplateA() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(getJedisConnectionFactoryA());
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    // b 連接RedisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplateB() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(getJedisConnectionFactoryB());
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

//    // 監聽數據庫?此時監聽所有
//    @Bean
//    public ChannelTopic expiredTopic() {
//        return new ChannelTopic("__keyevent@0__:expired");
//    }
}

 

接口測試文件

@RestController
@RequestMapping(value = "ce")
public class RedisListenerTest {

    @Autowired
    @Resource(name = "redisTemplateA")  // 選擇需要的RedisTemplate,此處與redis配置文件中的相對應
    private RedisTemplate redisTemplateA;

    @Autowired
    @Resource(name = "redisTemplateB")
    private RedisTemplate redisTemplateB;


    @GetMapping(value = "shi")
    public void ceShi() {
        // 向 a redis庫添加
        redisTemplateA.opsForValue().set("a號數據庫", "1", 5, TimeUnit.SECONDS);
        // 向 b redis 庫添加
        redisTemplateB.opsForValue().set("b號數據庫", "1", 5, TimeUnit.SECONDS);
    }
}

執行測試文件5秒後

 

做這個需求的時候看到了kafka,JMS相關,需要繼續學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章