高并发三宝:缓存、异步、队排好。
秒杀商品上架
定时任务上架
/**
* 秒杀商品定时上架
* 每天晚上3点上架最近三天需要秒杀的商品
*/
@Slf4j
@Service
public class SeckillSkuScheduled {
@Autowired
SeckillService seckillService;
@Autowired
RedissonClient redissonClient;
/**
* 保证幂等性,分布式锁
*/
@Scheduled(cron = "0 0 3 * * ?")
public void uploadSeckillSku() {
RLock lock = redissonClient.getLock(SeckillConstant.SECKILL_UPLOAD_LOCK);
lock.lock(10, TimeUnit.SECONDS);
try {
seckillService.uploadSeckillSku();
log.info("商品上架成功......");
} finally {
lock.unlock();
}
}
}
public void uploadSeckillSku() {
R r = couponFeignService.getSeckillSessions();
if ((int) r.get("code") == 0) {
// 远程获取所有需要上架的秒杀活动
List<SeckillSessionVO> sessions = r.getData("sessions", new TypeReference<List<SeckillSessionVO>>() {});
// 缓存redis
// 1、缓存活动信息
cacheSessionInfo(sessions);
// 2、缓存活动关联的商品信息
cacheSessionSkuInfo(sessions);
}
}
private void cacheSessionInfo(List<SeckillSessionVO> sessions) {
sessions.stream().forEach(session -> {
long startTime = session.getStartTime().getTime();
long endTime = session.getEndTime().getTime();
String key = SeckillConstant.SESSION_CACHE_PREFIX + startTime + "_" + endTime;
if (!stringRedisTemplate.hasKey(key)) {
List<SeckillSkuRelationVO> relationSkus = session.getRelationSkus();
List<String> skuIds = relationSkus.stream().map(relationSku -> {
return relationSku.getPromotionSessionId()+"_"+relationSku.getSkuId();
}).collect(Collectors.toList());
stringRedisTemplate.opsForList().leftPushAll(key, skuIds);
}
});
}
private void cacheSessionSkuInfo(List<SeckillSessionVO> sessions) {
sessions.stream().forEach(session -> {
BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SeckillConstant.SECKILLSKU_CACHE_PREFIX);
List<SeckillSkuRelationVO> relationSkus = session.getRelationSkus();
relationSkus.stream().forEach(relationSku -> {
// 判断信号量的key是否存在
if (!ops.hasKey(relationSku.getPromotionSessionId()+"_"+relationSku.getSkuId())) {
SeckillSkuVO seckillSku = new SeckillSkuVO();
// 商品秒杀信息
seckillSku.setSkuSeckillInfo(relationSku);
// 商品详细详细信息
R r = productFeignService.getSkuInfo(relationSku.getSkuId());
if ((int) r.get("code") == 0) {
SkuInfoVO skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoVO>() {});
seckillSku.setSkuInfo(skuInfo);
}
// 商品秒杀时间
seckillSku.setStartTime(session.getStartTime().getTime());
seckillSku.setEndTime(session.getEndTime().getTime());
// 秒杀token
String token = UUID.randomUUID().toString().replace("-", "");
seckillSku.setToken(token);
// 引入分布式信号量
RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHONE + token);
semaphore.trySetPermits(relationSku.getSeckillCount().intValue());
ops.put(relationSku.getPromotionSessionId()+"_"+relationSku.getSkuId(), JSON.toJSONString(seckillSku));
}
});
});
}
秒杀流程
处理秒杀请求
@GetMapping("/seckill")
public R seckill(@RequestParam("killId") String killId,
@RequestParam("randomCode") String randomCode,
@RequestParam("num") String num) {
String orderSn = seckillService.seckill(killId, randomCode, Integer.parseInt(num));
return R.ok().put("orderSn", orderSn);
}
@Override
public String seckill(String killId, String randomCode, Integer num) {
// 获取当前登录用户
MemberTO member = LoginInterceptor.threadLocal.get();
BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SeckillConstant.SECKILLSKU_CACHE_PREFIX);
String json = ops.get(killId);
if (!StringUtils.isEmpty(json)) {
SeckillSkuVO seckillSkuVO = JSON.parseObject(json, SeckillSkuVO.class);
// 校验合法性
long startTime = seckillSkuVO.getStartTime();
long endTime = seckillSkuVO.getEndTime();
long currentTime = new Date().getTime();
// 校验时间合法性
if (currentTime >= startTime && currentTime <= endTime) {
// 校验随机码和商品
String token = seckillSkuVO.getToken();
String redisKillId = seckillSkuVO.getSkuSeckillInfo().getPromotionSessionId() + "_" + seckillSkuVO.getSkuSeckillInfo().getSkuId();
if (token.equals(randomCode) && redisKillId.equals(killId) && num <= seckillSkuVO.getSkuSeckillInfo().getSeckillLimit().intValue()) {
// 校验是否购买过(幂等性处理)
String redisKey = member.getId() + "_" + redisKillId;
long ttl = endTime - currentTime;
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
if (flag) {
RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHONE + token);
try {
boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
// 创建订单号
String orderSn = IdWorker.getTimeId();
// TODO 秒杀成功,发送MQ消息
return orderSn;
} catch (InterruptedException e) {
log.error(e.toString());
}
}
}
}
}
return null;
}