springboot整合redis过期key监听实现订单过期操作

业务场景说明

对于订单问题,那些下单了但是没有去支付的(占单情况),不管对于支付宝还是微信都有订单的过期时间设置,但是对于我们自己维护的订单呢。两种方案:被动修改,主动修改。这里仅仅说明对于主动修改的监听实现

  • 修改redis的配置文件redis.conf(好像不改也可以)

    K:keyspace事件,事件以__keyspace@__为前缀进行发布;        
    E:keyevent事件,事件以__keyevent@__为前缀进行发布;        
    g:一般性的,非特定类型的命令,比如del,expire,rename等;       
    $:字符串特定命令;        
    l:列表特定命令;        
    s:集合特定命令;        
    h:哈希特定命令;        
    z:有序集合特定命令;        
    x:过期事件,当某个键过期并删除时会产生该事件;        
    e:驱逐事件,当某个键因maxmemore策略而被删除时,产生该事件;        
    A:g$lshzxe的别名,因此”AKE”意味着所有事件
    
  • pom依赖坐标的引入

    
    <dependency>
      <groupId>redis.clientsgroupId>
      <artifactId>jedisartifactId>
      <version>${jedis.version}version>
    dependency>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-data-redisartifactId>
      <version>${spring.boot.version}version>
    dependency>
    

    这里可能会存在的依赖冲突问题是与io.netty

  • RedisConfig的配置

    package test.bo.work.config.redis;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    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;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    import test.bo.work.util.StringUtil;
    
    
    /**
     * @author xiaobo
     */
    @Configuration
    @EnableAutoConfiguration
    public class JedisConfig {
        private static final Logger LOGGER = LoggerFactory.getLogger(JedisConfig.class);
    
        @Value("${spring.redis.host}")
        private String host;
    
        @Value("${spring.redis.port}")
        private int port;
    
        @Value("${spring.redis.password}")
        private String password;
    
        @Value("${spring.redis.timeout}")
        private int timeout;
    
        @Value("${spring.redis.jedis.pool.max-active}")
        private int maxActive;
    
        @Value("${spring.redis.jedis.pool.max-wait}")
        private int maxWait;
    
        @Value("${spring.redis.jedis.pool.max-idle}")
        private int maxIdle;
    
        @Value("${spring.redis.jedis.pool.min-idle}")
        private int minIdle;
    
        @Bean
        public JedisPool redisPoolFactory() {
            try {
                JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
                jedisPoolConfig.setMaxIdle(maxIdle);
                jedisPoolConfig.setMaxWaitMillis(maxWait);
                jedisPoolConfig.setMaxTotal(maxActive);
                jedisPoolConfig.setMinIdle(minIdle);
                // JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
                String pwd = StringUtil.isBlank(password) ? null : password;
                JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, pwd,6);
                LOGGER.info("初始化Redis连接池JedisPool成功!地址: " + host + ":" + port);
                return jedisPool;
            } catch (Exception e) {
                LOGGER.error("初始化Redis连接池JedisPool异常:" + e.getMessage());
            }
            return null;
        }
    
        @Bean
        public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
            RedisMessageListenerContainer container = new RedisMessageListenerContainer();
            container.setConnectionFactory(connectionFactory);
            return container;
        }
    
    
    }
    
  • 监听器实现

    package test.bo.work.config.redis;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.data.redis.connection.Message;
    import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
    import org.springframework.data.redis.listener.PatternTopic;
    import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    import org.springframework.stereotype.Component;
    
    import java.nio.charset.StandardCharsets;
    
    
    /**
     * @author xiaobo
     */
    @Component
    @Slf4j
    public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    
      public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
        log.info("Redis Key Expiration Listener has been initialized.");
      }
    
      @Override
      public void onMessage(Message message, byte[] pattern) {
        String expiredKey = message.toString();
        log.error(expiredKey);
          // 判断是否是想要监听的过期key
          if (expiredKey.startsWith(KuoCaiConstants.ORDER_REDIS_PREFIX)) {
            // 根据过期redisKey获取订单号
            String transactionOrderId = expiredKey.substring(KuoCaiConstants.ORDER_REDIS_PREFIX.length());
            transactionOrderService.updateTransactionOrderStatusByRedis(Long.valueOf(transactionOrderId));
    }
      }
    }
    
    

    【注意】可能存在的问题(监听事件失效)

    • 自己自定义了库,如下代码

       @Override
          protected void doRegister(RedisMessageListenerContainer listenerContainer) {
              // 针对db6进行监听
              listenerContainer.addMessageListener(this, new PatternTopic("__keyevent@6__:expired"));
          }
      

      这种情况,如果你存入的key不在db6,那么你就看不到监听触发事件(这里并不是失效,只是说可能出现的你认为失效的情况)

    • 使用了默认的连接工厂,但是配置文件中又没有相关定义

      首先解释一下,默认情况下,Spring Boot使用Jedis作为Redis客户端,并且会自动根据application.properties或application.yml配置文件中的spring.redis属性来创建连接工厂。如果没有指定这些属性,则会使用默认的localhost:6379作为Redis服务器地址。

      如果你想要连接到其他的Redis服务器,可以在配置文件中设置spring.redis.hostspring.redis.port属性来指定Redis服务器的地址和端口号。

      对于以上情况可以自定义工厂然后注入即可(上面的JedisConfig.java文件)

      @Bean
      public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration =
          new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setDatabase(db);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        redisStandaloneConfiguration.setPort(port);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
      }
      
      @Bean
      public RedisMessageListenerContainer container(JedisConnectionFactory jedisConnectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory);
        return container;
      }
      

    【警告】存在的问题

    • Redis的Key事件通知机制默认是异步的,即Redis会在Key过期时发送事件通知给所有监听者,但是不能保证监听者一定会及时接收到通知。如果您的应用程序需要在Key过期后立即处理相关操作,可能需要使用其他方式来实现。

    • 在Redis中,Key的过期时间只是一个近似的时间,它并不是精确的,因此不能保证过期时间到达时就一定会立即过期。如果您需要在Key过期后立即处理相关操作,建议您使用其他方式来实现,例如使用定时任务或轮询方式检查过期Key。

    • 在Redis中,Key的过期时间不能被取消或重置。如果您在设计时考虑到Key的过期时间可能需要修改,建议您使用其他方式来实现。

    • 当Redis中的Key被持久化到磁盘上时,过期时间可能会受到影响,因为过期时间的计算是基于系统时间的,如果系统时间发生变化,过期时间可能会出现不准确的情况。因此,建议您使用其他方式来处理需要精确过期时间的场景。

你可能感兴趣的:(redis,springboot,redis,spring,boot,java)