秒杀系统如何保证数据库不崩溃以及防止商品超卖(秒杀系统库存设计)

  本篇文章为你整理了秒杀系统如何保证数据库不崩溃以及防止商品超卖(秒杀系统库存设计)的详细内容,包含有秒杀系统数据库设计 秒杀系统库存设计 秒杀系统流程 秒杀系统扣减库存设计 秒杀系统如何保证数据库不崩溃以及防止商品超卖,希望能帮助你了解 秒杀系统如何保证数据库不崩溃以及防止商品超卖。

  1、应用场景

  电商商城,商家上架了一个秒杀活动,早上10点开始,商品A参与秒杀,一共有20个库存,预计10W的人去抢。

  

  2、面临问题

  高并发、库存不可超卖

  

  3、问题解决

  1)高并发,我们不能把所有的请求都去数据库查商品详情,查商品库存,这样数据库会顶不住,很容易的我们就想到了用Redis解决;

  2)库存超卖问题,这个问题主要是由于用户在同时读取到的库存均为大于0,从而认为我们该商品还没被秒完,继续创建了订单,导致了商品超卖了。

  

  4、编码实现

  1、数据库新建两张表

  秒杀订单

  

CREATE TABLE `ms_order` (

 

   `ms_order_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 订单ID,

   `created_time` datetime DEFAULT NULL COMMENT 创建时间,

   `order_price` decimal(12,2) DEFAULT NULL COMMENT 订单总价,

   `state` tinyint(1) DEFAULT 1 COMMENT 订单状态 1未支付 2已支付 3已发货 4已收货 -1已取消,

   `pay_time` datetime DEFAULT NULL COMMENT 支付时间,

   `fh_time` datetime DEFAULT NULL COMMENT 发货时间,

   PRIMARY KEY (`ms_order_id`)

  ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT=秒杀订单;

 

  

  秒杀商品

  

CREATE TABLE `ms_product` (

 

   `ms_product_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 秒杀商品ID,

   `product_name` varchar(100) DEFAULT NULL COMMENT 商品名称,

   `origin_price` decimal(12,2) DEFAULT NULL COMMENT 商品原价,

   `ms_price` decimal(12,2) DEFAULT NULL COMMENT 秒杀价,

   `product_img` varchar(255) DEFAULT NULL COMMENT 商品图片,

   `state` tinyint(1) DEFAULT NULL COMMENT 商品状态 1已上架 -1已下架,

   `product_summary` varchar(255) DEFAULT NULL COMMENT 商品描述,

   `product_details` text COMMENT 商品详情,

 

  

 PRIMARY KEY (`ms_product_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT=秒杀商品;

 

  

  2、设置商品库存,正式的流程肯定是由后台添加商品时初始化,这边为了方便,直接用Redis可视化工具插入了商品,秒杀商品ID为1的设置20个库存,同时数据库也要设置20个库存,利于我们分析扣减库存是否一致

  

  

  3、敲代码

  1)写一个下单接口

  

@PostMapping(value = "/add")

 

   public ResultMsg add(HttpServletRequest request, MsOrder msOrder,Long ms_product_id) {

   String interfaceName = "下单测试";

   try {

   User user = getUser();

   return new ResultMsg(true, msOrderService.insert(msOrder, user,ms_product_id));

   } catch (ServiceRuntimeException e) {

   return fail(e);

   } catch (Exception e) {

   return error(interfaceName, e, request);

   }

 

  

  2)逻辑处理

  利用lua脚本减库存,lua脚本如下

  

local isExist = redis.call(exists, KEYS[1]);

 

  if (tonumber(isExist) 0) then

   local goodsNumber = redis.call(get, KEYS[1]);

   if (tonumber(goodsNumber) 0) then

   redis.call(decr,KEYS[1]);

   return 1;

   else

   redis.call(del, KEYS[1]);

   return 0;

   end;

  return -1;

  end;

 

  

  lua配置类

  

@Configuration

 

  public class LuaConfiguration {

   @Bean

   public DefaultRedisScript Long redisScript() {

   DefaultRedisScript Long redisScript = new DefaultRedisScript ();

   redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/Stock.lua")));

   redisScript.setResultType(Long.class);

   return redisScript;

  }

 

  

  扣减Redis中对应的商品库存

  

@Component

 

  public class LuaReduceStock {

   @Resource

   private DefaultRedisScript Long redisScript;

   @Resource

   private StringRedisTemplate stringRedisTemplate;

   * 减库存

   * @param key

   * @return

   public boolean reduceStock(String key){

   List String keys = new ArrayList ();

   keys.add(key);

   Long result = stringRedisTemplate.execute(redisScript,keys,"100");

   return result 0;

  }

 

  

  业务处理

  

public boolean insert(MsOrder msOrder, User user,Long ms_product_id){

 

   Assert.notNull(ms_product_id,"购买商品不能为空");

   boolean b = luaReduceStock.reduceStock(RedisConstants.MSSTOCK+ms_product_id);

   if(b){

   //最终抢到库存的用户,可以发送一条消息到队列中,进行异步下单扣减库存等。

   Map map = new HashMap();

   map.put("ms_product_id",ms_product_id);

   amqpTemplate.convertAndSend(RabbitConstants.MS_QUEUE,map);

   return true;

   }else{

   serviceError("手慢了,商品已被抢光啦!!!");

   return true;

   }

 

  

  异步下单,扣减库存

  

@Component

 

  @RabbitListener(queues = RabbitConstants.MS_QUEUE)

  public class MsOrderHandler {

  
Long ms_product_id = Long.valueOf(map.get("ms_product_id").toString());

   MsProductDTO msProductDTO = msProductService.findById(ms_product_id);

   MsOrder msOrder = new MsOrder();

   msOrder.setCreated_time(new Date());

   msOrder.setOrder_price(msProductDTO.getMs_price());

   msOrder.setState(1);

   msOrderMapper.insert(msOrder);

   MsProduct msProduct = new MsProduct();

   msProduct.setStock(-1);

   msProduct.setMs_product_id(ms_product_id);

   msProductMapper.updateStock(msProduct);

   }catch (Exception e){

   e.printStackTrace();

  }

 

  

  5、jmeter测试

  

  

  

  查看执行结果,生成了20条订单,并且秒杀商品1的库存减为了0,大功告成!!!

  

  

  6、总结

  使用Lua脚本调用redis,可以确保操作的原子性,很好地避免了库存超卖的问题,并且保证了系统的性能,减少网络开销。

  

  以上就是秒杀系统如何保证数据库不崩溃以及防止商品超卖(秒杀系统库存设计)的详细内容,想要了解更多 秒杀系统如何保证数据库不崩溃以及防止商品超卖的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: