基于Key过期失效与定时任务实现订单超时未支付自动失效

一、将订单缓存至Redis

1. 创建数据表user_order

create table user_order(
	id int(11) not null auto_increment,
    order_no varchar(100) not null comment "订单编号",
    user_id int(11) not null comment "用户id",
    is_active smallint(4) default 1 comment "状态(1=有效;0=无效)",
    create_time datetime default current_timestamp comment "下单时间",
    update_time timestamp null default null on update current_timestamp,
    primary key(id),
    unique key idx_order_no(order_no) using btree
    ) engine=InnoDB auto_increment=5 default charset=utf8 comment="用户下单记录表";
    
insert into user_order(order_no, user_id, is_active)  values ("Book20191222001", 10010, 1);
insert into user_order(order_no, user_id, is_active)  values ("Book20191223001", 10011, 1);
insert into user_order(order_no, user_id, is_active)  values ("Book20191223003", 10012, 0);

2. 通过逆向工程生成对应的entity, mapper接口和映射文件

3. UserOrderService.java

@EnableScheduling
@Service
public class UserOrderService {

    private static final Logger log= LoggerFactory.getLogger(UserOrderService.class);


    //雪花算法工具-用于分布式环境(高并发)生成全局唯一ID的工具
    private static final Snowflake SNOWFLAKE=new Snowflake(3,2);

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

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


    @Autowired
    private UserOrderMapper userOrderMapper;

    @Autowired
    private RedisTemplate redisTemplate;


    //下单服务
    @Transactional(rollbackFor = Exception.class)
    public String putOrder(UserOrder entity) throws Exception{
        //用户下单-入库
        String orderNo=SNOWFLAKE.nextIdStr();//利用雪花算法生成全局唯一的id
        entity.setOrderNo(orderNo);//设置编号
        entity.setOrderTime(new Date());//设置时间
        int res=userOrderMapper.insertSelective(entity);
        if (res>0){
            //TODO:插入db之后,将订单编号塞入缓存中,同时设置好ttl
            redisTemplate.opsForValue().set(RedisUserOrderPrefix+orderNo,entity.getId(),UserOrderTimeOut,TimeUnit.SECONDS);
        }
        return orderNo;
    }
}

其中利用雪花算法来生成全局唯一ID(订单编号)

4. UserOrderController.java

@RestController
@RequestMapping("user/order")
public class UserOrderController {

    private static final Logger log= LoggerFactory.getLogger(UserOrderController.class);

    @Autowired
    private UserOrderService userOrderService;


    //下单
    @RequestMapping(value = "put",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public BaseResponse put(@RequestBody @Validated UserOrder userOrder, BindingResult result){
        String checkRes= ValidatorUtil.checkResult(result);
        if (StrUtil.isNotBlank(checkRes)){
            return new BaseResponse(StatusCode.InvalidParams.getCode(),checkRes);
        }
        BaseResponse response=new BaseResponse(StatusCode.Success);
        try {
            log.info("--用户下单:{}",userOrder);

            String res=userOrderService.putOrder(userOrder);
            response.setData(res);
        }catch (Exception e){
            log.error("--用户下单-发生异常:",e.fillInStackTrace());
            response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }

}

二、利用定时任务调度对订单失效

借助定时任务调度将已失效的订单在数据中修改,其实就是定时任务定时拉取DB中未失效的订单(按照下单时间升序),和缓存一一作对比,判断缓存中hasKey是否为True

@Async表示开启多个线程去异步执行

@Scheduled:服务只要一启动,就会自动地去调用。 


    //TODO:定时任务调度-拉取出 有效 + 未支付 的订单列表,前往缓存查询订单是否已失效
    @Scheduled(cron = "0/1 * * * * ?")
    @Async("threadPoolTaskExecutor")
    public void schedulerCheckOrder(){
        try {
            List<UserOrder> orders=userOrderMapper.selectUnPayOrders();
            if (orders!=null && !orders.isEmpty()){
                orders.forEach(entity -> {
                    String key=RedisUserOrderPrefix+entity.getOrderNo();
                    if (!redisTemplate.hasKey(key)){
                        //TODO:表示缓存中该订单编号已经过期失效了,我们需要前往数据库失效该订单...
                        userOrderMapper.unActiveOrder(entity.getId());

                        log.info("----缓存中当前订单超过了TTL未支付,故而失效该表中的对应记录:orderNo={}",entity.getOrderNo());
                    }
                });
            }
        }catch (Exception e){
            log.error("定时任务调度-拉取出 有效 + 未支付 的订单列表,前往缓存查询订单是否已失效-发生异常:",e);
        }
    }

如果有定时任务调度的话,需要加上一个线程池的配置,多个调度会被分配至多个线程去执行。

@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;
    }
}

缺陷

如果成功下单但是却迟迟未支付的订单量过大时,将很占内存~可能会演变为悲伤的故事。

改进:采用RabbitMQ的死信队列;采用Redisson的延迟队列...

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值