秒杀场景模拟(基础模型)
一 · 场景1 乐观锁与悲观锁更新库存 + 令牌桶限流
@RequestMapping("/createOptimisticOrder/{sid}")
@ResponseBody
public String createOptimisticOrder(@PathVariable int sid) {
LOGGER.info("等待时间" + rateLimiter.acquire());
int id;
try {
id = orderService.createOptimisticOrder(sid);
LOGGER.info("购买成功,剩余库存为: [{}]", id);
} catch (Exception e) {
LOGGER.error("购买失败:[{}]", e.getMessage());
return "购买失败,库存不足";
}
return String.format("购买成功,剩余库存为:%d", id);
}
@Override
public int createOptimisticOrder(int sid) {
Stock stock = checkStock(sid);
boolean success = saleStockOptimistic(stock);
if (!success){
throw new RuntimeException("过期库存值,更新失败");
}
createOrder(stock);
return stock.getCount() - (stock.getSale()+1);
}
@RequestMapping("/createPessimisticOrder/{sid}")
@ResponseBody
public String createPessimisticOrder(@PathVariable int sid) {
int id;
try {
id = orderService.createPessimisticOrder(sid);
LOGGER.info("购买成功,剩余库存为: [{}]", id);
} catch (Exception e) {
LOGGER.error("购买失败:[{}]", e.getMessage());
return "购买失败,库存不足";
}
return String.format("购买成功,剩余库存为:%d", id);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public int createPessimisticOrder(int sid){
Stock stock = checkStockForUpdate(sid);
saleStock(stock);
createOrder(stock);
return stock.getCount() - (stock.getSale());
}
二 · 场景2 要求验证的抢购接口 + 单用户限制访问频率
@RequestMapping(value = "/createOrderWithVerifiedUrlAndLimit", method = {
RequestMethod.GET})
@ResponseBody
public String createOrderWithVerifiedUrlAndLimit(@RequestParam(value = "sid") Integer sid,
@RequestParam(value = "userId") Integer userId,
@RequestParam(value = "verifyHash") String verifyHash) {
int stockLeft;
try {
int count = userService.addUserCount(userId);
LOGGER.info("用户截至该次的访问次数为: [{}]", count);
boolean isBanned = userService.getUserIsBanned(userId);
if (isBanned) {
return "购买失败,超过频率限制";
}
stockLeft = orderService.createVerifiedOrder(sid, userId, verifyHash);
LOGGER.info("购买成功,剩余库存为: [{}]", stockLeft);
} catch (Exception e) {
LOGGER.error("购买失败:[{}]", e.getMessage());
return e.getMessage();
}
return String.format("购买成功,剩余库存为:%d", stockLeft);
}
@Override
public boolean getUserIsBanned(Integer userId) {
String limitKey = CacheKey.LIMIT_KEY.getKey() + "_" + userId;
String limitNum = stringRedisTemplate.opsForValue().get(limitKey);
if (limitNum == null) {
LOGGER.error("该用户没有访问申请验证值记录,疑似异常");
return true;
}
return Integer.parseInt(limitNum) > ALLOW_COUNT;
}
@Override
public int createVerifiedOrder(Integer sid, Integer userId, String verifyHash) throws Exception {
LOGGER.info("请自行验证是否在抢购时间内,假设此处验证成功");
String hashKey = CacheKey.HASH_KEY.getKey() + "_" + sid + "_" + userId;
System.out.println(hashKey);
String verifyHashInRedis = stringRedisTemplate.opsForValue().get(hashKey);
if (!verifyHash.equals(verifyHashInRedis)) {
throw new Exception("hash值与Redis中不符合");
}
LOGGER.info("验证hash值合法性成功");
User user = userMapper.selectByPrimaryKey(userId.longValue());
if (user == null) {
throw new Exception("用户不存在");
}
LOGGER.info("用户信息验证成功:[{}]", user.toString());
Stock stock = stockService.getStockById(sid);
if (stock == null) {
throw new Exception("商品不存在");
}
LOGGER.info("商品信息验证成功:[{}]", stock.toString());
saleStockOptimistic(stock);
LOGGER.info("乐观锁更新库存成功");
createOrderWithUserInfoInDB(stock, userId);
LOGGER.info("创建订单成功");
return stock.getCount() - (stock.getSale()+1);
}
三 · 场景3 缓存(缓存库存)+ 数据库
1. 在秒杀之前将需要参加活动的商品信息导入缓存中,并使用文件夹进行分离,以防数据过多造成脏数据,将数据进行统一管理。
2. 创建缓存,在记录购买人UID信息并将缓存中的商品的库存扣减。
情况 1 :下单接口:先删除缓存,再更新数据库
@RequestMapping("/createOrderWithCacheV1/{sid}")
@ResponseBody
public String createOrderWithCacheV1(@PathVariable int sid) {
int count = 0;
try {
stockService.delStockCountCache(sid);
orderService.createPessimisticOrder(sid);
} catch (Exception e) {
LOGGER.error("购买失败:[{}]", e.getMessage());
return "购买失败,库存不足";
}
LOGGER.info("购买成功,剩余库存为: [{}]", count);
return String.format("购买成功,剩余库存为:%d", count);
}
@Override
public void delStockCountCache(int id) {
String hashKey = CacheKey.STOCK_COUNT.getKey() + "_" + id;
stringRedisTemplate.delete(hashKey);
LOGGER.info("删除商品id:[{}] 缓存", id);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public int createPessimisticOrder(int sid){
Stock stock = checkStockForUpdate(sid);
saleStock(stock);
createOrder(stock);
return stock.getCount() - (stock.getSale());
}
情况 2 :下单接口:先更新数据库,再删缓存
@RequestMapping("/createOrderWithCacheV2/{sid}")
@ResponseBody
public String createOrderWithCacheV2(@PathVariable int sid) {
int count = 0;
try {
orderService.createPessimisticOrder(sid);
stockService.delStockCountCache(sid);
} catch (Exception e) {
LOGGER.error("购买失败:[{}]", e.getMessage());
return "购买失败,库存不足";
}
LOGGER.info("购买成功,剩余库存为: [{}]", count);
return String.format("购买成功,剩余库存为:%d", count);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public int createPessimisticOrder(int sid){
Stock stock = checkStockForUpdate(sid);
saleStock(stock);
createOrder(stock);
return stock.getCount() - (stock.getSale());
}
@Override
public void delStockCountCache(int id) {
String hashKey = CacheKey.STOCK_COUNT.getKey() + "_" + id;
stringRedisTemplate.delete(hashKey);
LOGGER.info("删除商品id:[{}] 缓存", id);
}
情况 3 :下单接口:先删除缓存,再更新数据库,缓存延时双删
@RequestMapping("/createOrderWithCacheV3/{sid}")
@ResponseBody
public String createOrderWithCacheV3(@PathVariable int sid) {
int count;
try {
stockService.delStockCountCache(sid);
count = orderService.createPessimisticOrder(sid);
LOGGER.info("完成下单事务");
cachedThreadPool.execute(new delCacheByThread(sid));
} catch (Exception e) {
LOGGER.error("购买失败:[{}]", e.getMessage());
return "购买失败,库存不足";
}
LOGGER.info("购买成功,剩余库存为: [{}]", count);
return String.format("购买成功,剩余库存为:%d", count);
}
@Override
public void delStockCountCache(int id) {
String hashKey = CacheKey.STOCK_COUNT.getKey() + "_" + id;
stringRedisTemplate.delete(hashKey);
LOGGER.info("删除商品id:[{}] 缓存", id);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public int createPessimisticOrder(int sid){
Stock stock = checkStockForUpdate(sid);
saleStock(stock);
createOrder(stock);
return stock.getCount() - (stock.getSale());
}
情况 4 :下单接口:先更新数据库,再删缓存,删除缓存失败重试,通知消息队列
@RequestMapping("/createOrderWithCacheV4/{sid}")
@ResponseBody
public String createOrderWithCacheV4(@PathVariable int sid) {
int count;
try {
count = orderService.createPessimisticOrder(sid);
LOGGER.info("完成下单事务");
stockService.delStockCountCache(sid);
sendToDelCache(String.valueOf(sid));
} catch (Exception e) {
LOGGER.error("购买失败:[{}]", e.getMessage());
return "购买失败,库存不足";
}
LOGGER.info("购买成功,剩余库存为: [{}]", count);
return "购买成功";
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Override
public int createPessimisticOrder(int sid){
Stock stock = checkStockForUpdate(sid);
saleStock(stock);
createOrder(stock);
return stock.getCount() - (stock.getSale());
}
@Override
public void delStockCountCache(int id) {
String hashKey = CacheKey.STOCK_COUNT.getKey() + "_" + id;
stringRedisTemplate.delete(hashKey);
LOGGER.info("删除商品id:[{}] 缓存", id);
}
private void sendToDelCache(String message) {
LOGGER.info("这就去通知消息队列开始重试删除缓存:[{}]", message);
this.rabbitTemplate.convertAndSend("delCache", message);
}
四 · 场景4 异步处理
情况 1 :下单接口:异步处理订单
@RequestMapping(value = "/createOrderWithMq", method = {
RequestMethod.GET})
@ResponseBody
public String createOrderWithMq(@RequestParam(value = "sid") Integer sid,
@RequestParam(value = "userId") Integer userId) {
try {
Integer count = stockService.getStockCount(sid);
if (count == 0) {
return "秒杀请求失败,库存不足.....";
}
LOGGER.info("有库存:[{}]", count);
JSONObject jsonObject = new JSONObject();
jsonObject.put("sid", sid);
jsonObject.put("userId", userId);
sendToOrderQueue(jsonObject.toJSONString());
return "秒杀请求提交成功";
} catch (Exception e) {
LOGGER.error("下单接口:异步处理订单异常:", e);
return "秒杀请求失败,服务器正忙.....";
}
}
@Override
public Integer getStockCount(int sid) {
Integer stockLeft;
stockLeft = getStockCountByCache(sid);
LOGGER.info("缓存中取得库存数:[{}]", stockLeft);
if (stockLeft == null) {
stockLeft = getStockCountByDB(sid);
LOGGER.info("缓存未命中,查询数据库,并写入缓存");
setStockCountCache(sid, stockLeft);
}
return stockLeft;
}
private void sendToOrderQueue(String message) {
LOGGER.info("这就去通知消息队列开始下单:[{}]", message);
this.rabbitTemplate.convertAndSend("orderQueue", message);
}
情况 2 :下单接口:异步处理订单2
@RequestMapping(value = "/createUserOrderWithMq", method = {
RequestMethod.GET})
@ResponseBody
public String createUserOrderWithMq(@RequestParam(value = "sid") Integer sid,
@RequestParam(value = "userId") Integer userId) {
try {
Boolean hasOrder = orderService.checkUserOrderInfoInCache(sid, userId);
if (hasOrder != null && hasOrder) {
LOGGER.info("该用户已经抢购过");
return "你已经抢购过了,不要太贪心.....";
}
LOGGER.info("没有抢购过,检查缓存中商品是否还有库存");
Integer count = stockService.getStockCount(sid);
if (count == 0) {
return "秒杀请求失败,库存不足.....";
}
LOGGER.info("有库存:[{}]", count);
JSONObject jsonObject = new JSONObject();
jsonObject.put("sid", sid);
jsonObject.put("userId", userId);
sendToOrderQueue(jsonObject.toJSONString());
return "秒杀请求提交成功";
} catch (Exception e) {
LOGGER.error("下单接口:异步处理订单异常:", e);
return "秒杀请求失败,服务器正忙.....";
}
}
@Override
public Boolean checkUserOrderInfoInCache(Integer sid, Integer userId) throws Exception {
String key = CacheKey.USER_HAS_ORDER.getKey() + "_" + sid;
LOGGER.info("检查用户Id:[{}] 是否抢购过商品Id:[{}] 检查Key:[{}]", userId, sid, key);
return stringRedisTemplate.opsForSet().isMember(key, userId.toString());
}
@Override
public Integer getStockCount(int sid) {
Integer stockLeft;
stockLeft = getStockCountByCache(sid);
LOGGER.info("缓存中取得库存数:[{}]", stockLeft);
if (stockLeft == null) {
stockLeft = getStockCountByDB(sid);
LOGGER.info("缓存未命中,查询数据库,并写入缓存");
setStockCountCache(sid, stockLeft);
}
return stockLeft;
}
private void sendToOrderQueue(String message) {
LOGGER.info("这就去通知消息队列开始下单:[{}]", message);
this.rabbitTemplate.convertAndSend("orderQueue", message);
}
通用
@RequestMapping(value = "/checkOrderByUserIdInCache", method = {
RequestMethod.GET})
@ResponseBody
public String checkOrderByUserIdInCache(@RequestParam(value = "sid") Integer sid,
@RequestParam(value = "userId") Integer userId) {
try {
Boolean hasOrder = orderService.checkUserOrderInfoInCache(sid, userId);
if (hasOrder != null && hasOrder) {
return "恭喜您,已经抢购成功!";
}
} catch (Exception e) {
LOGGER.error("检查订单异常:", e);
}
return "很抱歉,你的订单尚未生成,继续排队。";
}
private class delCacheByThread implements Runnable {
private int sid;
public delCacheByThread(int sid) {
this.sid = sid;
}
public void run() {
try {
LOGGER.info("异步执行缓存再删除,商品id:[{}], 首先休眠:[{}] 毫秒", sid, DELAY_MILLSECONDS);
Thread.sleep(DELAY_MILLSECONDS);
stockService.delStockCountCache(sid);
LOGGER.info("再次删除商品id:[{}] 缓存", sid);
} catch (Exception e) {
LOGGER.error("delCacheByThread执行出错", e);
}
}
}