百味皆苦 java后端开发攻城狮

Redis监听key失效

2020-03-06
百味皆苦

订单支付超时自动失效

“Redis的Key的TTL一到就自动从缓存中剔除” 这个过程是Redis底层自动触发的,而在我们的程序、代码里是完全感知不到的,因为我们得借助某种机制来帮助我们主动地去检测Redis缓存中那些Key已经失效了,而且,我们希望这种检测可以是“近实时”的!

基于Redis的Key失效/存活时间TTL +定时任务调度(用于主动定时的触发,去检测缓存Redis那些失效了的Key,而且希望Cron可以设置得足够合理,实现“近实时”的功效)!

用户下单流程

用户提交过来的信息经过处理生成相应的订单号,然后将该订单记录插入数据库、插入缓存Redis,并设置对应的Key的存活时间TTL

用户下单记录表

CREATE TABLE `user_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  `order_no` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '订单编号',
  `pay_status` tinyint(255) DEFAULT '1' COMMENT '支付状态(1=未支付;2=已支付;3=已取消)',
  `is_active` tinyint(255) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
  `order_time` datetime DEFAULT NULL COMMENT '下单时间',
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='用户下单记录表';

下单逻辑

//用户下单service
@EnableScheduling
@Service
public class UserOrderService {
    private static final Logger log= LoggerFactory.getLogger(UserOrderService.class);

    //雪花算法生成订单编号
    private static final Snowflake SNOWFLAKE=new Snowflake(3,2);

    //存储至缓存的用户订单编号的前缀
    private static final String RedisUserOrderPrefix="SpringBootRedis:UserOrder:";

    //用户订单失效的时间配置 - 30min
		private static final Long UserOrderTimeOut=30L;

    @Autowired
    private UserOrderMapper userOrderMapper;

    @Autowired
		private StringRedisTemplate stringRedisTemplate;

    /**下单服务
     * @param entity
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public String putOrder(UserOrder entity) throws Exception{
        //用户下单-入库
        String orderNo=SNOWFLAKE.nextIdStr();
        entity.setOrderNo(orderNo);
        entity.setOrderTime(DateTime.now().toDate());
        int res=userOrderMapper.insertSelective(entity);

        if (res>0){
            //TODO:入库成功后-设定TTL 插入缓存 - TTL一到,该订单对应的Key将自动从缓存中被移除(间接意味着:延迟着做某些时间)
            stringRedisTemplate.opsForValue().set(RedisUserOrderPrefix+orderNo,entity.getId().toString(),UserOrderTimeOut, TimeUnit.MINUTES);
        }
        return orderNo;
	}
}

定时监测订单状态

开启一个定时任务调度,拉取出数据库DB中“有效且未支付的订单列表”,然后逐个遍历,前往缓存Redis查看该订单编号对应的Key是否还存在,如果不存在,说明TTL早已到期,也就间接地说明了用户在规定的时间TTL内没有完成整个支付流程,此时需要前往数据库DB中失效其对应的订单记录

在UserOrderService中添加定时任务逻辑

    //TODO:定时任务调度-每五分钟拉取出 有效 + 未支付 的订单列表,前往缓存查询订单是否已失效
    @Scheduled(cron = "0 0/5 * * * ?")
    @Async("threadPoolTaskExecutor")
    public void schedulerCheckOrder(){
        try {
            List<UserOrder> list=userOrderMapper.selectUnPayOrders();
            if (list!=null && !list.isEmpty()){

                list.forEach(entity -> {
                    final String orderNo=entity.getOrderNo();
                    String key=RedisUserOrderPrefix+orderNo;
                    if (!stringRedisTemplate.hasKey(key)){
                        //TODO:表示缓存中该Key已经失效了,即“该订单已经是超过30min未支付了,得需要前往数据库将其失效掉”
                        userOrderMapper.unActiveOrder(entity.getId());
                        log.info("缓存中该订单编号已经是超过指定的时间未支付了,得需要前往数据库将其失效掉!orderNo={}",orderNo);
                    }
                });
            }
        }catch (Exception e){
            log.error("定时任务调度-拉取出 有效 + 未支付 的订单列表,前往缓存查询订单是否已失效-发生异常:",e.fillInStackTrace());
        }
    }

其中的@Async(“threadPoolTaskExecutor”),代表该定时任务将采用“异步”+“线程池~多线程”的方式进行执行,其配置如下所示:

//多线程配置
@Configuration
public class ThreadConfig {

    @Bean("threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setKeepAliveSeconds(10);
        executor.setQueueCapacity(8);

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

Comments

Content