一种消息系统.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相关,需要继续学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值