文章目录
一、接口优化过程
1.1 过程要点
- Redis预减库存减少数据库访问
- 内存标记减少Redis访问
- 请求先入队缓冲,异步下单,增强用户体验。
- RabbitMQ安装与Spring Boot继承
- Nginx水平扩展
- 分库分表中间件 mycat
二、具体实现
2.1 减少数据库的访问
- 系统初始化,商品库存数量添加到Redis中
- 收到请求,Redis预减库存,库存不足,直接返回,否则进入3
- 请求入队,立即返回排队中
- 请求出队,生成订单,减少库存
- 客户端轮询是否秒杀成功
2.2 系统初始化,商品库存数量添加到Redis中
需要实现InitializingBean 接口,重写afterPropertiesSet()方法
public class miaoshaController implements InitializingBean {
@Autowired
GoodsService goodsService;
@Autowired
OrderService orderService;
@Autowired
MiaoshaService miaoshaService;
@Autowired
OrderDao orderDao;
@Autowired
RedisService redisService;
//系统初始化做的事情
@Override
public void afterPropertiesSet() throws Exception {
//系统启动的时候把商品的数量加载进内存
List<GoodsVo> goodsVos = goodsService.selectAll();
if(goodsVos.isEmpty()){
return;
}
for(GoodsVo goodsVo : goodsVos){
redisService.set(GoodsKey.ByGoodsStock,""+goodsVo.getId(),goodsVo.getStockCount());
}
}
}
2.3 判断用户是否登录
if(miaoshaUser == null){
return Result.error(CodeMsg.SESSION_ERROR);
}
2.4 预减库存
原来是直接去数据库查询库存是否足够秒杀,秒杀则直接秒杀。现在是在Redis中找到商品的库存信息,进行预减库存。
//预减库存
Long count = redisService.decr(GoodsKey.ByGoodsStock,""+goodsId);
if(count < 0){
model.addAttribute("errmsg", CodeMsg.MIAOSHA_OVER_ERROR.getMsg());
return "miaosha_fail";
}
2.5 判断是否重复秒杀
根据商品的ID和当前用户登录的ID从换缓存中和数据库中查询,看是否生成订单,来判断是否重复秒杀
GoodsVo goodsVo = goodsService.seleteReferGoods(goodsId);
OrderInfo orderInfo = null;
OrderInfo orderInfo1 = redisService.get(MiaoshaOrderKey.OrderByManyID, "" + miaoshaUser.getId() + goodsVo.getId(), OrderInfo.class);
if(orderInfo1 != null){
model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
return Result.error(CodeMsg.MIAOSHA_FAIL);
}else{
orderInfo = orderService.getOrderInfo(miaoshaUser.getId(),goodsId);
}
if(orderInfo != null){
model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
return Result.error(CodeMsg.MIAOSHA_FAIL);
}
三、秒杀和RabbitMq结合
3.1 秒杀信息对应的实体类
public class MiaoshaMsg {
private MiaoshaUser user;
private long goodsId;
}
3.2 秒杀信息入队
先将秒杀信息转换为JSO格式的字符串,然后入队,秒杀实现在消息队列的服务端
@Service
public class RabbitMqSend {
@Autowired
AmqpTemplate amqpTemplate;
@Autowired
RedisService redisService;
public void send(MiaoshaMsg miaoshaMsg){
String messgae = redisService.BeanToString(miaoshaMsg);
amqpTemplate.convertAndSend(MqConfig.Queue1,messgae);
}
}
3.3 消息队列服务端实现秒杀
- 判断库存是否足够
- 判断是否重复秒杀
- 减库存,下订单
- 秒杀成功,将秒杀成功信息写到redis中
@RabbitListener(queues = MqConfig.Queue1)
public String Receive(String str, Model model){
MiaoshaMsg miaoshaMsg = redisService.StringToBean(str, MiaoshaMsg.class);
long goodsId = miaoshaMsg.getGoodsId();
MiaoshaUser miaoshaUser = miaoshaMsg.getUser();
//1.判断库存是否足够
GoodsVo goodsVo = goodsService.seleteReferGoods(goodsId);
Integer stockCount = goodsVo.getStockCount();
if(stockCount <= 0){
model.addAttribute("errmsg", CodeMsg.MIAOSHA_OVER_ERROR.getMsg());
return "miaosha_fail";
}
//2.判断是否重复秒杀
//3.判断是否重复秒杀
//先从缓存中取
OrderInfo orderInfo = null;
OrderInfo orderInfo1 = redisService.get(MiaoshaOrderKey.OrderByManyID, "" + miaoshaUser.getId() + goodsVo.getId(), OrderInfo.class);
if(orderInfo1 != null){
model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
return "miaosha_fail";
}else{
orderInfo = orderService.getOrderInfo(miaoshaUser.getId(),goodsId);
}
if(orderInfo != null){
model.addAttribute("errmsg",CodeMsg.REPEATE_MIAOSHA.getMsg());
return "miaosha_fail";
}
//System.out.println("步骤3");
//4.减库存,下订单,写入秒杀订单,需要在事务中操作
OrderInfo miaoshaOrderInfo = miaoshaService.RealMiaosha(miaoshaUser, goodsVo);
model.addAttribute("orderinfo",miaoshaOrderInfo);
model.addAttribute("goods",goodsVo);
//System.out.println("步骤4");
//5.秒杀成功,存入redis中
redisService.set(MiaoshaOrderKey.OrderByManyID,""+miaoshaUser.getId() + goodsVo.getId(),orderInfo);
log.info("ID是: " + miaoshaMsg.getUser().getId() + "的用户秒杀了商品ID为" + miaoshaMsg.getGoodsId() + "的商品" );
return "order_detail";
}
3.4 秒杀时做一个内存标记
作用:提示商品已经秒杀光了,这样服务端在处理消息时就很可以直接返回秒杀失败。
实现:通过判断数据库减少一个数量后,商品的库存是否为负数来判断
//减少库存,Goods表的库存得减少,MiaoshaGoods表的库存也得减少
OrderInfo orderInfo = null;
if( goodsVo.getStockCount()<= 0){
setGoodsOver(goodsVo.getId());
}
3.5 需要在前端轮询秒杀的结果,对应的后端是
通过result来返回处理的结果:
@RequestMapping(value = "/result",method = RequestMethod.GET)
@ResponseBody
public Result<Long> miaosharesult(Model model,MiaoshaUser miaoshaUser,@RequestParam("godsId")Long goodsId){
model.addAttribute("user",miaoshaUser);
if(miaoshaUser == null){
return Result.error(CodeMsg.SESSION_ERROR);
}
long result = miaoshaService.getMiaoshaResult(miaoshaUser.getId(),goodsId);
return Result.success(result);
}
秒杀成功返回商品的Id,失败返回-1,正在处理返回0;
//秒杀成功返回商品Id
//失败返回-1
//正在处理返回0
public long getMiaoshaResult(Long id, Long goodsId) {
MiaoshaOrder miaoshaOrder = miaoshaOrderService.selectByUserIDGoodsID(id, goodsId);
if(miaoshaOrder != null){
return miaoshaOrder.getOrderId();
//一种情况秒杀失败,一种是正在处理
}else {
boolean isOver = getGoodsOver(goodsId);
if(isOver){
//商品是不是秒杀完
return -1;
}else {
//没有就继续轮询
return 0;
}
}
}
3.7 访问redis优化
在每次的业务逻辑,都需要去redis中取库存的信息,即使库存不足,也要去redis中访问库存的信息,内存标志的作用就是使库存不足时,就不去redis中预减库存了。
系统初始化的时候设置一个标志,存到map中
for(GoodsVo goodsVo : goodsVos){
redisService.set(GoodsKey.ByGoodsStock,""+goodsVo.getId(),goodsVo.getStockCount());
map.put(goodsVo.getId(),false);
}
预减库存之前先去map中判断标志
Boolean flag = (Boolean) map.get(goodsId);
if(flag){
return Result.error(CodeMsg.MIAOSHA_OVER_ERROR);
}
库存不足,设置标志
if(count < 0){
map.put(goodsId,true);
model.addAttribute("errmsg", CodeMsg.MIAOSHA_OVER_ERROR.getMsg());
return Result.error(CodeMsg.MIAOSHA_FAIL);
}