如何在过期事件中访问spring data redis store对象?

Posted

技术标签:

【中文标题】如何在过期事件中访问spring data redis store对象?【英文标题】:How to acess spring data redis stored object at the expiration event? 【发布时间】:2020-09-19 04:22:32 【问题描述】:

我使用 Spring Data Redis 将购物车存储在 Redis 中特定时间。使用 @TimeToLive 注释的过期属性设置 Cart 对象的生存时间,如下面的代码所述。 我设置了KeyExpirationEventMessageListener 类型来监听过期事件,以便在过期事件中处理额外的工作。我能够从过期对象的触发事件中获取密钥,并且我试图在过期时使用 spring 数据存储库访问它或其幻像对象,但没有结果。它返回一个空对象,这意味着原始对象对象很可能已被删除。我不知道这是否是正确的方法。但是,有没有办法在到期时或在它被删除以处理移动工作之前获取到期对象?

@RedisHash("cart")
public class Cart implements Serializable 
    @Id
    @Indexed
    private String id;
    private long customerId;
    private Set<CartLine> lines = new HashSet<>();

    @TimeToLive
    private long expiration;



public interface ShoppingCartRepository extends CrudRepository<Cart, String> 




    @Component
    public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener 

        private RedisTemplate<?, ?> redisTemplate;
        private ShoppingCartRepository repository;
        public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer,
                                          RedisTemplate redisTemplate, ShoppingCartRepository repository) 
            super(listenerContainer);
            this.redisTemplate = redisTemplate;
            this.repository = repository;
        

        @Override
        public void onMessage(Message message, byte[] pattern) 
            String key = new String(message.getBody());
            try 
                String id = extractId(key);
                Optional<ShoppingCart> cart = repository.findById(id);
             catch(Exception e) 
                logger.info("something went wrong  ====>  " + e.getStackTrace());
            
        
        private String extractId(String key)
            String keyPrefix = ShoppingCart.class.getAnnotation(RedisHash.class).value();
            return key.substring((keyPrefix + ":").length());
        
    

【问题讨论】:

【参考方案1】:

使用 Spring boot 2.3.4.RELEASE 的示例

@RedisHash("cart")
@AllArgsConstructor
@Getter
@Setter
public class Cart implements Serializable 
    @Id
    @Indexed
    private String id;
    private String customerName;
    @TimeToLive
    private long expiration;

public interface ShoppingCartRepository extends CrudRepository<Cart, String> 

@SpringBootApplication
@EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP)
public class RedisExpirationApplication 

    public static void main(String[] args) 
        SpringApplication.run(RedisExpirationApplication.class, args);
    

    @Bean
    public CommandLineRunner run(ShoppingCartRepository shoppingCartRepository) 
        return args -> 
            // TTL 1 second
            shoppingCartRepository.save(new Cart("some-id", "My Customer Name", 1));
            // wait 5 seconds
            Thread.sleep(5000L);
        ;
    

    @Component
    public class CartListener 
        // need to enable redis notifications, inside redis-cli type:
        // config set notify-keyspace-events KEA
        @EventListener
        public void anything(RedisKeyExpiredEvent<Cart> expiredCart) 
            Cart cart = (Cart) expiredCart.getValue();
            System.out.println(cart.getId());
            System.out.println(cart.getCustomerName());
        
    

【讨论】:

感谢 Nilzao,与 Spring Boot 2.3.4 完美集成。这工作得很好。 @ROUISSIMohamedALi 您是使用属性enableKeyspaceEvents 在代码中启用键空间事件还是从 Redis 控制台手动启用? @AbhijitSarkar 我是从 Redis 控制台手动完成的。【参考方案2】:

我的场景里曾经有一个用例,其实可以使用RedisKeyExpiredEvent,

RedisKeyExpiredEvent [0] 是发布的特定于 Redis 的 ApplicationEvent 当 Redis 中的特定键过期时。它可以保持的价值 密钥旁边的过期密钥。

您可以继续执行以下操作。

@SpringBootApplication
@EnableRedisRepositories(considerNestedRepositories = true, enableKeyspaceEvents = EnableKeyspaceEvents.ON_STARTUP)
static class Config 

    /**
     * An @link ApplicationListener that captures @link RedisKeyExpiredEvents and just prints the value to the
     * console.
     *
     * @return
     */
    @Bean
    ApplicationListener<RedisKeyExpiredEvent<Person>> eventListener() 
        return event -> 
            System.out.println(String.format("Received expire event for key=%s with value %s.",
                    new String(event.getSource()), event.getValue()));
        ;
    

您可以在 [1] 找到示例实现。

[0]https://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/core/RedisKeyExpiredEvent.html#getValue-- [1]https://github.com/christophstrobl/next-level-redis-with-spring/blob/master/src/test/java/org/example/repository/RepsoitoryTests.java#L57

【讨论】:

我在起始类的顶部添加了@EnableRedisRepositories(considerNestedRepositories = true, enableKeyspaceEvents = EnableKeyspaceEvents.ON_STARTUP),并在我的自定义购物车对象中添加了 eventListener() Bean。但是当它过期时,不会调用 lambda 函数。我也尝试将其用作组件,但 onApplicationEvent 方法没有被调用 您是否尝试正确调试,您确定代码已正确实现。你能给我任何日志或示例实现,我可以检查它在我的本地实例上运行。 是的,我也尝试过调试。这是一个基本工作的示例代码库bitbucket.org/marouissi/springdataredis/src/master 嘿 Anuj 你有机会看到这个代码示例吗?你能做到吗

以上是关于如何在过期事件中访问spring data redis store对象?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot:过期后如何生成新的访问令牌?

如何使用 Spring Data Redis 为过期键启用键空间通知

如何将默认过期的 RedisCacheManager 迁移到 Spring Data Redis 2.0?

在基于 Spring 的 Web 应用程序中处理会话过期事件

如何在 Spring Boot 测试中模拟会话关闭/过期?

Spring boot实现监听Redis key过期事件