订单支付超时自动失效
“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;
}
}