【问题标题】:Spring Data Redis Expire KeySpring Data Redis 过期密钥
【发布时间】:2016-04-25 21:35:31
【问题描述】:

我有一个 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"/>

这个redisTemplate 在我的ServiceImpl 类中使用。

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的问题?

【问题讨论】:

  • docs.spring.io/spring-data/data-redis/docs/1.5.2.RELEASE/api/… 没有说任何关于 uniqueKey 的内容。您可以将密钥传递给过期
  • 是的,你是对的。在这个 url 中没有说任何关于 uniqueKey 的内容。但是在实现中,当我使用 uniqueKey 时,这个 uniqueKey 就过期了。但是当我使用密钥时,此密钥不会过期。
  • 唯一的密钥被认为是整个哈希......所以可以使这个哈希的密钥过期。除非您需要手动删除哈希键和值。

标签: java spring hibernate redis


【解决方案1】:

实际上,您不能为 Redis Hash 中的单个键过期或设置 TTL。您只能将完整的哈希过期或设置 TTL。如果你想支持这一点,你必须改变你的数据结构。

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

据我所知,redis 关心的是性能而不是功能。它会 破坏了 redis 中内存高效哈希实现的目的。 由于哈希键值字段并不总是表示为完整的 特色 redis 对象(它们可以在散列时存储为线性数组 小以节省内存),因此哈希键字段不能有TTL。

此链接 Allow to set an expiration on hash field 也可能会帮助您更改数据结构以处理到期

【讨论】:

    【解决方案2】:

    实际上你可以通过Redisson Redis Java Client 使用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);
    

    【讨论】:

      【解决方案3】:

      我正在使用 Redis 版本 3.2.100。

      代替 redis 模板,使用 Redis 缓存管理器,将 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;
          }
          }
      

      【讨论】:

        【解决方案4】:

        您可以为此目的采用 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);
            }
        }
        

        【讨论】:

          【解决方案5】:

          要为keys设置TTL,你可以创建多个cacheManager的bean并为单个bean设置TTL。然后根据您的使用,您可以使用所需的缓存管理器。 这是我已经实现的。

          @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 也是如此

          【讨论】:

            【解决方案6】:

            我正在使用 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 个幻像键)...
            • 是的,它确实添加了一个幻像键。但如果是从 Spring Boot 或类似的访问商店,那将不是问题。
            【解决方案7】:

            虽然我迟到了,但我迟到了。

            在密钥级别设置TTL值是不可能的,因为org.springframework.data.redis.cache.RedisCacheManager没有提供任何方法来配置密钥中的TTL值,尽管他们已经为cache级别提供了它。以下步骤将帮助您在 cachedefault 级别配置 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获得

            【讨论】:

              【解决方案8】:

              我遇到了同样的问题。不同之处在于使用 Jedis 客户端。我解决了它改变 UniqueKey 和 Key 的位置。 对于您的示例,它将是这样的:

              redisService.sadd(key, uniqueKey);
              redis.expire(key, expirationTime);
              

              【讨论】:

                【解决方案9】:

                使用 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();
                }
                

                【讨论】:

                  【解决方案10】:

                  您好,不确定这是否仍然有效,但唯一正确的答案是由提供的

                  桑托什·乔希

                  https://stackoverflow.com/a/34896731/4222461

                  正如他正确指出的那样,您不能使 redis 哈希中的单个密钥过期,只能使哈希本身过期。

                  https://github.com/redis/redis/issues/1042

                  尤其是https://github.com/redis/redis/issues/1042#issuecomment-45367109

                  【讨论】:

                    【解决方案11】:

                    简单而简短:通过 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();
                            }
                        }
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 2020-09-26
                      • 2020-04-19
                      • 1970-01-01
                      • 2012-08-04
                      • 2017-09-18
                      • 2023-03-04
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-02-21
                      相关资源
                      最近更新 更多