SpringBoot中监听redis失效key

需求

在线考试的一个交卷功能(商城的超时未支付订单也类似,不同之处在于商城的处理比较简单,失效就可以了,考试功能还需要在key失效后取回value做业务处理),用户整个答题流程中产生的数据存在redis中,当用户点交卷时,从redis中取出数据,做后续的评分、统计、存数据库等处理。如果用户未答完就非正常退出,要在到达考试结束时间时清除掉redis里面的数据,并执行后续的评分等操作。

处理方式

方式一:(本文采用的方式)

redis中对每次考试存两份数据(key1根据考试时长设定失效时间,key2永久有效),且value相同,当数据修改时,两份数据都做修改。编写一个监听器,当监听到key1失效时,从key2中取回数据做业务处理,然后删除key2。

方式二:

数据存入redis的同时启动一个定时任务,到时间后通过定时任务做业务处理及删除key。

方式三:(这种方式比较耗性能,不建议使用)

做一个频繁执行的定时任务,轮询检测是否考试超时。

环境及代码

一、代码地址:https://gitee.com/chrisfzh/dailytest

二、windows安装、配置、启动redis

1、下载地址:https://github.com/microsoftarchive/redis/releases

2、解压后,修改配置文件

设置redis密码

配置监听超时事件

在redis目录打开cmd命令行窗口,输入以下命令指定配置文件启动redis服务

redis-server.exe redis.wondows.conf

出现如下界面则启动成功

 

三、代码

1、项目目录结构

2、具体代码

如需具体代码请到码云拉去,已在第一步代码地址中注明,本文中仅介绍关键代码

存入redis业务代码

package com.chrisf.business.service.impl;

import com.chrisf.business.service.TestService;
import com.chrisf.config.SufConfig;
import com.chrisf.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private RedisCache redisCache;
    @Autowired
    private SufConfig sufConfig;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public String test01() {
        //redis中的key
        String key = "key";
        
        //value即为具体的业务数据
        String value = "value";
        
        //配置文件中配置的key2相对于key1的后缀
        String rediskey = sufConfig.getRediskey();
        
        //将key:value存入redis并设置失效时间为10秒
        redisCache.setCacheObject(key, value, 10, TimeUnit.SECONDS);
        
        //key1拼接后缀后成为key2,不设置失效时间,且value与key1的相同
        redisCache.setCacheObject(key + rediskey, value);

        return "ok";
    }
}

 监听器

package com.chrisf.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

@Configuration
public class RedisListenerConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}
package com.chrisf.redis;

import com.chrisf.config.RedisConfig;
import com.chrisf.config.SufConfig;
import org.springframework.beans.factory.annotation.Autowired;
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;

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    @Autowired
    private SufConfig sufConfig;

    @Autowired
    private RedisCache redisCache;

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

    /**
     * 针对redis数据失效事件,进行数据处理
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 失效key
        String expiredKey = message.toString();
        System.out.println("失效的key: " + expiredKey);
        
        //失效key拼接配置文件中的后缀后获取value
        String key = expiredKey + sufConfig.getRediskey();
        String value = (String) redisCache.getCacheObject(key);
        
        //TODO 拿到value去处理业务逻辑

        //调用删除方法删除key2
        redisCache.deleteObject(key);
    }
}

3、测试效果

触发之后,通过redis桌面工具查看是否添加了Key,并且验证失效时能否被监听到

 

4、踩到的坑

在测试该功能的时候,遇到key乱码问题,后来查找到原因,redis默认编码方式为ISO-8859-1,我的项目编码方式为utf-8,因此需要在序列化的时候做一些配置。

package com.chrisf.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置
 *
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}
package com.chrisf.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;

/**
 * Redis使用FastJson序列化
 *
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    @SuppressWarnings("unused")
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }

    public void setObjectMapper(ObjectMapper objectMapper)
    {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用 Spring Data Redis 提供的 @EnableRedisRepositories 注解,同时实现 RedisKeyExpirationListener 接口,并在 @Configuration 类添加监听器配置。 以下是代码片段示例: @Configuration @EnableRedisRepositories public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(jedisConnectionFactory()); return template; } @Bean public JedisConnectionFactory jedisConnectionFactory() { return new JedisConnectionFactory(); } @Bean public RedisMessageListenerContainer redisContainer() { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(jedisConnectionFactory()); return container; } @Bean RedisKeyExpirationListener keyExpirationListener() { return new RedisKeyExpirationListener(); } @Bean MessageListenerAdapter listenerAdapter(RedisKeyExpirationListener keyExpirationListener) { return new MessageListenerAdapter(keyExpirationListener); } @Bean RedisMessageListenerContainer keyExpirationContainer(RedisKeyExpirationListener keyExpirationListener, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(jedisConnectionFactory()); container.addMessageListener(listenerAdapter, new PatternTopic("__keyevent@*:expired")); return container; } private static class RedisKeyExpirationListener implements MessageListener { @Override public void onMessage(Message message, byte[] pattern) { String expiredKey = message.toString(); // 处理过期key逻辑 } } } 这里我们定义了一个 RedisContainer 和一个 RedisKeyExpirationListener,然后将 RedisKeyExpirationListener 获取到的过期 key 进行处理。ListenAdapter 和 keyExpirationContainer 主要是为了配置监听器,其"__keyevent@*:expired"用来监听所有 Redis 发生的 key 过期事件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值