秒杀系统之一:防止超卖(乐观锁)

2020-09-30  本文已影响0人  徒手說梦话

前提:只是为了了解和学习关于秒杀的学习顺便巩固学到的技术点

1.1 秒杀场景

1.2 为什么要做个系统

如果你的项目流量非常小,完全不用担心有并发的购买请求,那么做这样一个系统意义不大。但如果你的系统要像12306那样,接受高并发访问和下单的考验,那么你就需要一套完整的流程保护措施,来保证你系统在用户流量高峰期不会被搞挂了。

1.3 保护措施有哪些

2. 防止超卖

毕竟,你网页可以卡住最多是大家没参与到活动,上网口吐芬芳,骂你一波。但是你要是卖多了,本该拿到商品的用户可就不乐意了,轻则投诉你,重则找漏洞起诉赔偿。让你吃不了兜着走。

2.1 数据库表

-- ----------------------------
-- Table structure for stock
-- ----------------------------
DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
  `count` int(11) NOT NULL COMMENT '库存',
  `sale` int(11) NOT NULL COMMENT '已售',
  `version` int(11) NOT NULL COMMENT '乐观锁,版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for stock_order
-- ----------------------------
DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL COMMENT '库存ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2 分析业务

image-20200326184815171.png

2.3 开发代码

1. DAO代码
public interface StockDAO {
    Stock checkStock(Integer id);//校验库存
    void updateSale(Stock stock);//扣除库存
}

public interface OrderDAO {
    void createOrder(Order order);//创建订单
}

2. Service 代码
@Service
@Transactional
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDAO orderDAO;
    @Autowired
    private StockDAO stockDAO;
    @Override
    public Integer createOrder(Integer id) {
        //校验库存
        Stock stock = checkStock(id);
        //扣库存
        updateSale(stock);
        //下订单
        return createOrder(stock);
    }
    //校验库存
    private Stock checkStock(Integer id) {
        Stock stock = stockDAO.checkStock(id);
        if (stock.getSale().equals(stock.getCount())) {
            throw new RuntimeException("库存不足");
        }
        return stock;
    }
    //扣库存
    private void updateSale(Stock stock){
        stock.setSale(stock.getSale() + 1);
        stockDAO.updateSale(stock);
    }
    //下订单
    private Integer createOrder(Stock stock){
        Order order = new Order();
        order.setSid(stock.getId());
        order.setCreateDate(new Date());
        order.setName(stock.getName());
        orderDAO.createOrder(order);
        return order.getId();
    }
}
5. Controller代码
@RestController
@RequestMapping("stock")
public class StockController {
    @Autowired
    private OrderService orderService;
    //秒杀方法
    @GetMapping("sale")
    public String sale(Integer id){
        int orderId = 0;
        try{
            //根据商品id创建订单,返回创建订单的id
            orderId =  orderService.createOrder(id);
            System.out.println("orderId = " + orderId);
            return String.valueOf(orderId);
        }catch (Exception e){
            e.printStackTrace();
            return e.getMessage();
        }
    }
}

2.4 正常测试

在正常测试下发现没有任何问题

2.5 使用Jmeter进行压力测试

官网: https://jmeter.apache.org/

1. 介绍

Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。 它可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP 服务器, 等等。JMeter 可以用于对服务器、网络或对象模拟巨大的负载,来自不同压力类别下测试它们的强度和分析整体性能。另外,JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果

2. 安装Jmeter

# 1.下载jmeter
       https://jmeter.apache.org/download_jmeter.cgi
         下载地址:https://mirror.bit.edu.cn/apache//jmeter/binaries/apache-jmeter-5.2.1.tgz
# 2.解压缩
        backups    ---用来对压力测试进行备份目录
    bin        ---Jmeter核心执行脚本文件
    docs         ---官方文档和案例
    extras     ---额外的扩展
    lib        ---第三方依赖库
    licenses   ---说明
    printable_docs ---格式化文档

# 3.安装Jmeter
      0.要求: 必须事先安装jdk环境
      1.配置jmeter环境变量
          export JMETER_HOME=/Users/chenyannan/dev/apache-jmeter-5.2
                export PATH=$SCALA_HOME/bin:$JAVA_HOME/bin:$GRADLE_HOME/bin:$PATH:$JMETER_HOME/bin
        2.是配置生效
              source ~/.bash_profile
        3.测试jemeter

3. Jmeter使用

Don't use GUI mode for load testing !, only for Test creation and Test debugging.

For load testing, use CLI Mode (was NON GUI):

jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

& increase Java Heap to meet your test requirements:

Modify current env variable HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" in the jmeter batch file

Check : https://jmeter.apache.org/usermanual/best-practices.html

4. Jmeter压力测试

jmeter -n -t [jmx file](jmx压力测试文件) -l [results file](结果输出的文件) -e -o [Path to web report folder](生成html版压力测试报告)

2.6 乐观锁解决商品超卖问题

说明: 使用乐观锁解决商品的超卖问题,实际上是把主要防止超卖问题交给数据库解决,利用数据库中定义的version字段以及数据库中的事务实现在并发情况下商品的超卖问题。

0.校验库存的方法(不变)

//校验库存
private Stock checkStock(Integer id){
  Stock stock = stockDAO.checkStock(id);
  if(stock.getSale().equals(stock.getCount())){
    throw  new RuntimeException("库存不足!!!");
  }
  return stock;
}
<!--根据秒杀商品id查询库存-->
    <select id="checkStock" parameterType="int" resultType="Stock">
        select id,name,count,sale,version from stock
        where id = #{id}
    </select>

1. 更新库存方法改造

//扣除库存
private void updateSale(Stock stock){
    //在sql层面完成销量的+1  和 版本号的+  并且根据商品id和版本号同时查询更新的商品
    stockDAO.updateSale(stock);
}
 <update id="updateSale" parameterType="Stock">
        update stock set 
            sale=sale+1,
            version=version+1
         where 
            id =#{id}
            and 
            version = #{version}
 </update>

2. 创建订单

//创建订单
private Integer createOrder(Stock stock){
  Order order = new Order();
  order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
  orderDAO.createOrder(order);
  return order.getId();
}
<!--创建订单-->
<insert id="createOrder" parameterType="Order" useGeneratedKeys="true" keyProperty="id" >
  insert into stock_order values(#{id},#{sid},#{name},#{createDate})
</insert>

3. 完整的业务方法与Mapper.xml

上一篇下一篇

猜你喜欢

热点阅读