秒杀活动属于会员系统中的功能,但是秒杀的接口压力巨大所以需要把它独立出来。
秒杀活动表 :优惠系统中有定义秒杀场次,描述了秒杀活动id,活动开始时间,活动结束时间,这个在前端创建秒杀活动时有时间选择器,在查找秒杀活动列表页中记录可以添加关联表数据,根据活动id关联秒杀的商品表,来添加秒杀活动的商品信息。
秒杀商品上架:使用定时系统,每天凌晨3点将秒杀的商品上架到redis中,redis设计会使用list存放seckill:sessions:加活动开始时间_结束时间的值,他的值包括活动场次和skuid。由场次_skuid为键将秒杀商品skuid的详细信息存放在hash结构中,键为场次_skuid,值为skuid详细实体信息,并为并在根据场次_skuid生成信号量表示秒杀商品的数量。
随机码的生成作为库存数量锁的信号量的key来访问,只有拥有随机码组合的key才能访问信号量锁才能抢到信号量并减扣库存,否则抢失败。
商品上架线程非安全:假设三台秒杀服务,同时秒杀上架,当多个请求到来时在判断是否上架时在同一时间执行判断没有上架,则都开始上架,导致即便做了去重幂等操作在第一次多线程会导致多次上架,解决使用redisson的锁机制,在上架之前让不同服务的线程竞争同一把锁,谁得到就执行上架,没得到的就会下等待释放后上架,当相同的操作上架由于做了上架幂等,有当前key就放弃上架如下。
//TODO 保证幂等性问题
// @Scheduled(cron = "*/5 * * * * ? ")
@Scheduled(cron = "0 0 1/1 * * ? ")
public void uploadSeckillSkuLatest3Days() {
//1、重复上架无需处理
log.info("上架秒杀的商品...");
//分布式锁
RLock lock = redissonClient.getLock(upload_lock);
try {
//加锁
lock.lock(10, TimeUnit.SECONDS);
seckillService.uploadSeckillSkuLatest3Days();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
通过信号量为每个场次的秒杀数量设置锁功能,而且信号量是和商品上架后就加的,同时做了幂等,只有拿到新号量才真的可以秒杀到商品,在上架的时候会扫近三天的秒杀活动,然后再查skuid信息,完成在redis中上架,上架的时候有随机码,只有活动开始的时候请求才有效。其次有关时间的计算如下,秒杀的核心是避免超卖的现象,这里使用了信号量,只有获得信号量的线程才会往下执行做到了锁的功能。
private String startTime() {
LocalDate now = LocalDate.now();
LocalTime min = LocalTime.MIN;
LocalDateTime start = LocalDateTime.of(now, min);
//格式化时间
String startFormat = start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return startFormat;
}
/**
* 结束时间
* @return
*/
private String endTime() {
LocalDate now = LocalDate.now();
LocalDate plus = now.plusDays(2);
LocalTime max = LocalTime.MAX;
LocalDateTime end = LocalDateTime.of(plus, max);
//格式化时间
String endFormat = end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return endFormat;
}