Spring Data Redis 过期键

27

我有一个使用Spring Hibernate框架的应用程序。最近我加入了Spring Data Redis。

spring-servlet.xml
<!-- redis connection factory -->
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>

<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" 
    p:connection-factory-ref="jedisConnFactory"/>

我在ServiceImpl类中使用了这个redisTemplate

RedisServiceImpl

@Autowired
private RedisTemplate<String, T> redisTemplate;

public RedisTemplate<String, T> getRedisTemplate() {
    return redisTemplate;
}

public void setRedisTemplate(RedisTemplate<String, T> redisTemplate) {
    this.redisTemplate = redisTemplate;
}

现在我像这样在redisServer中添加了数据

public void putData(String uniqueKey, String key, Object results) {
        
    redisTemplate.opsForHash().put(uniqueKey, key, results);
}

现在我想要删除过期的键。

我在谷歌上搜索了一下,但是谷歌上所有的结果都跟这个差不多。

redisTemplate.expire(key, timeout, TimeUnit);
在这个过期方法中,我们需要提供 uniqueKey 而不是 key。但是我需要使 key 过期而不是 uniqueKey。请帮忙告诉我如何让 key 过期?

这个网址(http://docs.spring.io/spring-data/data-redis/docs/1.5.2.RELEASE/api/org/springframework/data/redis/core/RedisTemplate.html#expire(K,%20long,%20java.util.concurrent.TimeUnit))没有提到uniqueKey。您可以将key传递给expire函数。 - shazin
是的,你说得对。在这个url中没有提到uniqueKey。但是在实现中,当我使用uniqueKey时,这个uniqueKey过期了。但是当我使用key时,这个key没有过期。 - Akash Chavda
唯一键被视为整个哈希的关键,因此可以为此哈希过期键。除非您需要手动删除哈希键和值。 - Nebras
11个回答

20

我正在使用Spring Data Redis。

我正在使用@Redishash(timeToLive=300)注释来让我的实体在300秒后过期。

这是从我的pom.xml中摘录的部分内容。

...
...
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>
...
...
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
...
...

我的RedisConfig.class

@Configuration
@Log4j2
@EnableRedisRepositories(basePackageClasses = ConsentOTP.class)
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private Integer port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        log.info("=================================================================");
        log.info("redis config : {} : {} ", host, port);
        log.info("=================================================================");

        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
        config.setPassword(RedisPassword.of(password));
        return new JedisConnectionFactory(config);
    }

}

我的实体类是ConsentOtp.class

@RedisHash(value = "ConsentOTP", timeToLive = 300)
@Data
@NoArgsConstructor
public class ConsentOTP implements Serializable {

    private static final long serialVersionUID = 1708925807375596799L;

    private String id;
    private LocalDateTime timestamp;
    private String otp;

    public ConsentOTP(String personId, LocalDateTime timestamp, String otp) {
        this.id = personId;
        this.timestamp = timestamp;
        this.otp = otp;
    }
}

这是我的Redis存储库

public interface ConsentOtpRepository extends CrudRepository<ConsentOTP, String> {
    
}

此解决方案在Redis中添加幽灵键,而其他应用程序如Python读取这些键将获取同一键的两个值(1个原始值和1个幽灵值)。 - Arpan Sharma
是的,它会添加一个幻影键。但是如果从Spring Boot或类似的工具访问存储,则不会成为问题。 - Abdullah Khan

12

我正在使用Redis版本3.2.100。

使用Redis缓存管理器,而不是使用redis模板(redis template)。将红色模板(redistemplate)传递给缓存管理器(cacheManager),并使用其set expires属性(即String和Long类型的映射),您可以添加缓存名称并设置其到期时间,即存活时间(TTL)。

您可以使用cacheManager的setDefaultExpiration方法将相同的到期时间设置为所有缓存。

@SuppressWarnings({ "rawtypes", "unused" })
@Configuration
@EnableCaching(proxyTargetClass = true, mode = AdviceMode.ASPECTJ, order = 1)
@PropertySource("classpath:/application.properties")
public class CacheConfigImpl extends CachingConfigurerSupport {

    private @Value("${redis.ip}") String redisHost;
    private @Value("${redis.port}") int redisPort;

     private static final Map<String, Long> cacheMap = new HashMap<String, Long>();
    static {
        cacheMap.put("method1cache", 600L);
        cacheMap.put("method2cache", 600L);
        cacheMap.put("method3cache", 800L);
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
        redisConnectionFactory.setHostName(CustomPropertyLoader.getProperty("redis.ip"));
        redisConnectionFactory.setPort(Integer.parseInt(CustomPropertyLoader.getProperty("redis.port")));
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean(name = "RCacheManager")
    public CacheManager cacheManager(RedisTemplate redisTemplate) {

        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setExpires(cacheMap);
        cacheManager.setUsePrefix(true);
        final String redis_client_name = CustomPropertyLoader.getProperty("redis.client.name");
        cacheManager.setCachePrefix(new RedisCachePrefix() {
            private final RedisSerializer<String> serializer = new StringRedisSerializer();
            private final String delimiter = ":";

            public byte[] prefix(String cacheName) {
                return this.serializer
                        .serialize(redis_client_name.concat(this.delimiter).concat(cacheName).concat(this.delimiter));
            }
        });
        return cacheManager;
    }
    }

10

实际上,您可以使用Redisson Redis Java客户端,使用RMapCache对象来完成它。它提供了针对每个映射条目设置ttlmaxIdle的能力。例如:

// implements java.util.concurrent.ConcurrentMap interface
RMapCache<String, SomeObject> map = redisson.getMapCache("anyMap");

// ttl = 10 minutes, 
map.put("key1", new SomeObject(), 10, TimeUnit.MINUTES);

// ttl = 10 minutes, maxIdleTime = 10 seconds
map.put("key1", new SomeObject(), 10, TimeUnit.MINUTES, 10, TimeUnit.SECONDS);

5

要为键设置TTL,您可以创建多个cacheManager的bean,并为每个bean设置TTL。然后根据您的需求,您可以使用所需的cachemanager。

以下是我实现的内容。

@Configuration("cacheConfig")
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport{


    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        System.out.println("redisConnectionFactory");
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();

        // Defaults
        redisConnectionFactory.setHostName("127.0.0.1");
        redisConnectionFactory.setPort(6379);
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
        System.out.println("redisTemplate");
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(cf);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    @Primary
    public CacheManager cacheManager2(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        // Number of seconds before expiration. Defaults to unlimited (0)
        cacheManager.setDefaultExpiration(20);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }


    @Bean
    public CacheManager cacheManager1(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        // Number of seconds before expiration. Defaults to unlimited (0)
        cacheManager.setDefaultExpiration(60);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

}

要使用上述创建的缓存管理器bean,

@Cacheable(value = "users", key = "#userId.toString()", cacheManager ="cacheManager2")
    @RequestMapping(value = "/{userId}", method = RequestMethod.GET)
    public User getUser(@PathVariable String userId) {
        LOG.info("Getting user with ID {}.: "+userId);
      return userService.fetchUserDataonUsers(userId);
    }


@Cacheable(value = "users", key = "#userId.toString()", cacheManager ="cacheManager1")
    @RequestMapping(value = "data/{userId}", method = RequestMethod.GET)
    public String getUserData(@PathVariable String userId) {
        LOG.info("Getting user with ID getUserData {}.: "+userId);
      return userService.fetchUserDataonUsers(userId).toString();
    }

当我们在@Cacheable中定义cacheManager="cacheManager2"时,它将使用配置中为cacheManager2设置的TTL。对于cacheManager1也是同样的情况。

5

实际上,你不能使 Redis Hash 内的单个键过期或设置 TTL。你只能让整个哈希表过期或设置 TTL。如果您想要支持这一点,您需要更改数据结构。

这是为什么不可能的链接;以下是一些摘录: Redis expire

据我所知,Redis 更关注性能而非功能。这将破坏 Redis 中内存高效哈希实现的目的。由于哈希键值字段并不总是表示为全功能的 Redis 对象(当哈希表很小时,它们可以被存储为线性数组以节省内存),因此哈希键字段无法具有 TTL。

此外,允许在哈希字段上设置过期时间 可能会帮助您更改数据结构以处理到期。


3
您可以使用Quartz来实现Redis记录的ttl。 如果您使用Spring Boot,则它会自动为您配置Scheduler。因此,您可以直接将其自动装配到服务层。
@Autowired
private Scheduler scheduler;

然后你需要实现一个类似这样的工作(在本例中我使用了Spring Redis Data):
@Slf4j
@Component
public class RemoveExpiredRecordJob implements Job {

@Autowired
public RedisRepository redisRepository;

@Override
public void execute(JobExecutionContext jobExecutionContext) {
    String key = jobExecutionContext
            .getJobDetail()
            .getKey()
            .getName();
    redisRepository.deleteById(key);
    log.info("Record removed due timeout :: {}", key);
}

}

然后,您可以封装一些用于创建JobDetail和Trigger的逻辑。
@Component
public class SchedulerComponentBuilder {

    public JobDetail getJobDetail (String key, Class<? extends org.quartz.Job> clazz) {
        return JobBuilder.newJob().ofType(clazz)
                .storeDurably(false)
                .withIdentity(key)
                .withDescription("This key will be removed from Redis store when time expires.")
                .build();
    }

    public Trigger getTrigger(int ttl, JobDetail jobDetail) {
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.add(java.util.Calendar.SECOND, ttl);
        return TriggerBuilder.newTrigger().forJob(jobDetail)
                .withDescription("This trigger fires once to remove an expired record from Redis store.")
                .startAt(calendar.getTime())
                .build();
    }
}

最后,在将记录保存到Redis存储库之后,您需要安排一项任务,以便从中删除此记录(uniqueKey),如下所示:

@Autowired
private SchedulerComponentBuilder schedulerComponentBuilder;

private void schedule(String uniqueKey, int ttl) {
    try {
        JobDetail jobDetail = schedulerComponentBuilder.getJobDetail(uniqueKey, RemoveExpiredRecordJob.class);
        Trigger jobTrigger = schedulerComponentBuilder.getTrigger(ttl, jobDetail);
        scheduler.scheduleJob(jobDetail,jobTrigger);
        log.info("Job is scheduled :: {}", jobDetail);
    } catch (SchedulerException e) {
        log.error("Filed to schedule a job {}", e);
        throw new RuntimeException(e);
    }
}

2
虽然我晚了,但我还是要为后人发布这篇文章。
在键级别上设置TTL值是不可能的,因为org.springframework.data.redis.cache.RedisCacheManager没有提供任何方法来配置键的TTL值,尽管他们已经为缓存级别提供了此功能。以下步骤将帮助您配置缓存和默认级别的TTL时间。
1. 添加所需的Maven依赖项。
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
  1. 添加Redis配置,包括默认级别和缓存级别的TTL值。这里我们将TTL值设置为一个名为“photo”的示例缓存。
cache:
  host: localhost
  port: 6379
  default-ttl: 6000
  caches-ttl:
    photo: 3600
  1. 添加RedisCacheManager配置
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
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.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

@Configuration
@EnableCaching
public class RedisCacheConfiguration extends CachingConfigurerSupport {

    @Autowired
    private CacheConfigurationProperties cacheConfigurationProperties = null;

    private org.springframework.data.redis.cache.RedisCacheConfiguration createCacheConfiguration(long timeoutInSeconds) {
        return org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(timeoutInSeconds));
    }

    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory redisConnectionFactory) {
        Map<String, org.springframework.data.redis.cache.RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        if (Objects.nonNull(cacheConfigurationProperties.getCachesTTL())) {
            for (Entry<String, String> cacheNameAndTimeout : cacheConfigurationProperties.getCachesTTL().entrySet()) {
                cacheConfigurations.put(cacheNameAndTimeout.getKey(), createCacheConfiguration(Long.parseLong(cacheNameAndTimeout.getValue())));
            }
        }
        return RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(createCacheConfiguration(Long.parseLong(cacheConfigurationProperties.getDefaultTTL())))
                .withInitialCacheConfigurations(cacheConfigurations).build();
    }

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(cacheConfigurationProperties.getHost());
      redisStandaloneConfiguration.setPort(Integer.parseInt(cacheConfigurationProperties.getPort()));
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    @Configuration
    @ConfigurationProperties(prefix = "cache")
    @Data
    class CacheConfigurationProperties {
        private String port;
        private String host;
        private String defaultTTL;
        private Map<String, String> cachesTTL;
    }
}

完整文档可在Medium上找到。


0

使用Lettuce Redis客户端更新@Akanksha Sharma的answer代码。

@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
    return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}

@Bean
@Primary
public CacheManager cacheManager1(RedisConnectionFactory redisConnectionFactory) {
    
    return RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(60L)))
            .build();
    
}

@Bean
public CacheManager cacheManager2(RedisConnectionFactory redisConnectionFactory) {
    return RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1L)))
            .build();
}

0
简单明了:通过RedisConnection
public void saveInCache(String key, Object obj) {

    RedisConnection connection = connectionFactory.getConnection();
    try {
        connection.set(key.getBytes(), SerializationUtils.serialize(obj));
        //setting custom expiry runtime
        long expiresIn = obj.getExpiresAfter() - Instant.now().getEpochSecond(); 
        connection.expire(key.getBytes(), expiresIn);

    } catch (Exception e) {
        //handle any exception
    }
    finally {
        try {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }

        } catch (Exception e) {
            logger.error("Exception while closing redis connection : " + e.getMessage());
            e.printStackTrace();
        }
    }
}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接