秒杀场景实战分析流程图:
核心技术方案,通过redis缓存和分布式锁、lua脚本保证一致性、rabbitMq削峰填谷正式提交订单,查询商品详情已经分享过,此篇主要分享秒杀和正式下单功能。
1、秒杀接口:
控制层:
/**
* 秒杀接口,这里对redis做了预减库存,并把秒杀了的用户做了标识缓存
*/
@PostMapping("kill")
public HttpResponseBody kill(int killId) {
KillGoodsSpecPriceDetailVo killGoods = killGoodsService.detail(killId);
if (killGoods.getBegainTime().getTime() > System.currentTimeMillis()){
return HttpResponseBody.failResponse("抢购还未开始");
}
if (killGoods.getEndTime().getTime() < System.currentTimeMillis()){
return HttpResponseBody.failResponse("抢购已结束");
}
if (!killGoodsService.secKillByLock(killId,getSessionUserId())){
log.info("----抢购失败----");
return HttpResponseBody.failResponse("抢购失败");
}
return HttpResponseBody.successResponse("ok", killGoods);
}
业务层:
public boolean secKillByLock(int killId, String userId) {
//判断用户是否已经秒杀过
Boolean member = redisTemplate.opsForSet().isMember(KillConstants.KILLED_GOOD_USER + killId, userId);
if (member) {
logger.info("---------userId:" + userId + "----has secKilled");
return false;
}
String killGoodCount = KillConstants.KILL_GOOD_COUNT + killId;
//返回的数值,执行了lua脚本
Long stock = stock(killGoodCount, 1, STOCK_LUA);
if (stock == UNINITIALIZED_STOCK) {
Timer timer = null;
RedisLock redisLock = new RedisLock(redisTemplate, REDIS_LOCK);
try {
//如果竞争锁成功 如果其他线程没竞争锁成功,这里是阻塞的
if (redisLock.tryLock()) {
//锁续命
timer = continueLock(REDIS_LOCK);
stock = stock(killGoodCount, 1, STOCK_LUA);
if (stock == UNINITIALIZED_STOCK) {
KillGoodsPrice killGoodsPrice = iKillSpecManageService.selectByPrimaryKey(killId);
redisTemplate.opsForValue().set(killGoodCount, killGoodsPrice.getKillCount(), 60 * 60, TimeUnit.SECONDS);
//再次去执行lua脚本,扣减库存
stock = stock(killGoodCount, 1, STOCK_LUA);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (timer != null) {
timer.cancel();
}
//释放锁 。自己加的锁不能让别人释放,自己只能释放自己的锁
//这里要进行一个value值的比较,只要自己的value值相等才能释放
redisLock.unlock();
}
}
//如果是这种情况,秒杀成功
boolean flag = stock >= 0;
if (flag) {
redisTemplate.opsForSet().add(KillConstants.KILLED_GOOD_USER + killId, userId);
}
return flag;
}
lua脚本 STOCK_LUA:
/**
* 执行扣库存的脚本
*/
public static final String STOCK_LUA;
public static String STOCK_LUA_1 = "";
public static String STOCK_LUA_INCR = "";
static {
/**
*
* @desc 扣减库存Lua脚本
* 库存(stock)-1:表示不限库存
* 库存(stock)0:表示没有库存
* 库存(stock)大于0:表示剩余库存
*
* @params 库存key
* @return
* -3:库存未初始化
* -2:库存不足
* -1:不限库存
* 大于等于0:剩余库存(扣减之后剩余的库存)
* redis缓存的库存(value)是-1表示不限库存,直接返回1
*/
StringBuilder sb = new StringBuilder();
sb.append("if (redis.call('exists', KEYS[1]) == 1) then");//判断此商品是否存在
sb.append(" local stock