嚼一嚼秒杀系统(一)基于数据库乐观锁

嚼一嚼秒杀系统(一)基于数据库乐观锁

1、背景

最近突然对秒杀系统比较感兴趣,所以突发奇想来研究一下。其实在面试过程中也经常会遇到此类问题:如果你有一千个商品,怎么在最快的时间内卖出去,且不多卖不少卖。

下面我就基于这个常见的问题,逐步完成一个高性能的秒杀系统。

github地址:https://github.com/351524388/flash-sale

2、架构

本篇文章介绍demo1,使用到的技术如下:

  • SpringBoot-V2.3.1.RELEASE
    • JPA
      • @Version:数据版本号,实现乐观锁
      • @UpdateTimestamp:自动更新修改时间
    • Tomcat
      • server.tomcat.threads.max:200(默认情况)
  • MySQL-V5.7.20
  • nginx:反向代理,主要是通过80节点访问多个后台服务
  • jmeter:模拟高并发场景,聚合报告查看结果

3、代码实现

demo1实现的功能,是在高并发的情况下,正确的扣减库存。代码比较简单,分为以下几层:

  • entity:数据库模型,核心代码如下:

    @Entity
    @Table(name = "good_count_info")
    public class GoodCountInfo {
        /**
         * 自增主键
         */
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
        /**
         * 商品总数
         */
        private Long goodCount;
        /**
         * 数据版本号
         */
        @Version
        private Long versionNo;
        /**
         * 修改时间
         */
        @UpdateTimestamp
        private LocalDateTime modifyTime;
        // 省略 Setter、Getter 方法 ...
    }
    
  • repository:操作数据库,代码如下:

    @Repository
    public interface GoodCountRepository extends JpaRepository<GoodCountInfo, Integer> {
    }
    
  • service:业务操作,代码如下:

    @Service
    public class GoodCountServiceImpl implements IGoodCountService {
        @Value("${flash.sale.table.row.id:1}")
        private int rowId;
        @Autowired
        private GoodCountRepository goodCountRepository;
    
        @Override
        public Long getGoodCount() {
            return goodCountRepository.findAll().get(0).getGoodCount();
        }
    
        @Override
        @Transactional(rollbackFor = Throwable.class)
        public boolean subGoodCount() {
            GoodCountInfo goodCountInfo = goodCountRepository.getOne(rowId);
            if (goodCountInfo.getGoodCount() > 0) {
                goodCountInfo.setGoodCount(goodCountInfo.getGoodCount() - 1);
                goodCountRepository.save(goodCountInfo);
                return true;
            } else {
                return false;
            }
        }
    }
    
  • controller:接口层,代码如下:

    @RestController
    @RequestMapping("/flash-sale/goodCount")
    public class FlashSaleController {
        @Autowired
        private IGoodCountService goodCountService;
    
        @GetMapping
        public Object getGoodCountInfo() {
            return goodCountService.getGoodCount();
        }
    
        @PostMapping
        public Object subGoodCountInfo() {
            try {
                return goodCountService.subGoodCount();
            } catch (Exception e) {
                System.out.println("减库存失败");
                return false;
            }
        }
    }
    

这里再贴一下application.properties配置文件的内容:

spring.datasource.url=jdbc:mysql://localhost:3306/flash-sale?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate
#先启动节点1,修改这里的id,然后启动节点2
flash.sale.table.row.id=1

详细代码参见github:https://github.com/351524388/flash-sale

4、测试结果

这只是一个很简单的demo,但是再简单也需要能正确运行才行,于是我采用jmeter进行压测,通过聚合报告来分析结果,如果不太会用jmeter的读者,可以自行百度(因为我也是刚百度学会的)。

最终压测的结果如下:

  • 单节点:吞吐量100/s,异常率0%
  • 双节点:
    • 单行数据吞吐量200/s,异常率4%。多运行几次,可以达到300/s。
    • 两行数据吞吐量200/s,异常率2%。多运行几次,可以达到500/s。

根据结果可以看出,增加节点可以提高并发效率,但是异常率也会增加。如果将一千条数据分到不同的行中,我这里使用了两行,每行五百条,分开后可以有效提高并发效率,异常率也会降低。

另外有一点,就是服务启动以后,运行次数增加,性能会越来越好。所以秒杀之前对系统进行预热,对并发效率也会有所提升。毕竟Java号称是越跑越快。

5、总结

这是一个很简单的demo,后期会在这个基础上逐步完善,争取将单个节点的性能提升到极致。

不足之处,欢迎评论指出。

问题列表

  • 安装MySQL5.7.20的时候会报错,提示需要安装Microsoft Visual C++ 2013 Redistributable (x86),这时候只需要将这个插件的64位和32位都安装上就好了
  • Idea多一个服务启动多个实例:https://www.cnblogs.com/yg_zhang/p/12651584.html
  • Jmeter压测:https://blog.csdn.net/weixin_42118716/article/details/106498171
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值