秒杀流程
1. 合法性校验
- 秒杀时间校验:当前时间是否在秒杀时间范围内
- 接口幂等性校验:验证这个人是否已经买过了,去redis里使用setnx命令占位,过期时间=秒杀截止时间-当前时间
2. 获取信号量
- 信号量为秒杀的商品数
- 使用信号量的tryacquire方法实现分布式锁
3. 秒杀成功
- 发送订单信息到MQ,异步下单
- 直接返回秒杀成功结果,节省用户等待时间
4. 限流措施
- 前端限流,一些高并发的网站直接在前端页面开始限流,例如:小米的验证码设计
- nginx限流,直接负载部分请求到错误的静态页面:令牌算法 漏斗算法
- 网关限流,限流的过滤器。或者使用专业的限流组件sentinel
- 代码中使用分布式信号量
- rabbitmq限流(能者多劳:chanel.basicQos(1)),保证发挥所有服务器的性能。
代码
@Override
public String kill(String killId, Integer num) throws InterruptedException {
long s1 = System.currentTimeMillis();
//获取当前用户的信息
MemberResponseVo user = LoginUserInterceptor.loginUser.get();
//1、获取当前秒杀商品的详细信息从Redis中获取
BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
String skuInfoValue = hashOps.get(killId);
if (StringUtils.isEmpty(skuInfoValue)) {
return null;
}
//(合法性效验)
SeckillSkuRedisTo redisTo = JSON.parseObject(skuInfoValue, SeckillSkuRedisTo.class);
Long startTime = redisTo.getStartTime();
Long endTime = redisTo.getEndTime();
long currentTime = System.currentTimeMillis();
//判断当前这个秒杀请求是否在活动时间区间内(效验时间的合法性)
if (currentTime < startTime || currentTime > endTime) {
return null;
}
//3、验证购物数量是否合理和库存量是否充足
Integer seckillLimit = redisTo.getSeckillLimit();
//获取信号量
String seckillCount = redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE + randomCode);
Integer count = Integer.valueOf(seckillCount);
//判断信号量是否大于0,并且买的数量不能超过库存
if (count > 0 && num <= seckillLimit && count > num ) {
//4、验证这个人是否已经买过了(幂等性处理),如果秒杀成功,就去占位。userId-sessionId-skuId
//SETNX 原子性处理
String redisKey = user.getId() + "-" + skuId;
//设置自动过期(活动结束时间-当前时间)
Long ttl = endTime - currentTime;
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
if (aBoolean) {
//占位成功说明从来没有买过,分布式锁(获取信号量-1)
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
//TODO 秒杀成功,快速下单
boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
//保证Redis中还有商品库存
if (semaphoreCount) {
//创建订单号和订单信息发送给MQ
// 秒杀成功 快速下单 发送消息到 MQ 整个操作时间在 10ms 左右
String timeId = IdWorker.getTimeId();
SeckillOrderTo orderTo = new SeckillOrderTo();
orderTo.setOrderSn(timeId);
orderTo.setMemberId(user.getId());
orderTo.setNum(num);
orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
orderTo.setSkuId(redisTo.getSkuId());
orderTo.setSeckillPrice(redisTo.getSeckillPrice());
rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
return timeId;
}
}
}
return null;
}