course_grab 高并发抢课系统

本项目 fork 自 抢课系统 @Author YiMing-Chris,做了如下fix和优化

  • 原项目使用了Hibernate和Javax的相关注解,但并没有引入相关依赖。因为相关功能并不核心,这里直接删除了相关注解

  • 原项目的抢课接口存在超抢问题,redis的余量会小于0,而mysql的余量远大于0,存在很大的数据不一致性问题,真正抢到课的学生较少。这里使用了Lua脚本保证redis的读库存和减库存的原子性,并且在消费者端使用了分布式锁,防止超抢以及一人多抢问题

  • 原项目中rabbitmq没有配置交换机和队列的bind,导致消息无法被消费。这里在rabbitmq的管理界面采取了手动bind

  • 原项目中rabbitmq消费者端采用的是自动ack。这里使用了手动ack,提升消息消费的可靠性

  • 原项目没有对抢课接口进行限流。这里进行了限流

    有一个地方没想通,批量获取token的时候原项目用学号+数据库中经过两次MD5加密后的密码去登录,用加密后的密码去登录咋会登录成功呢,实测也会提示密码错误。这里直接把登录接口验证密码的代码注释掉了

实测数据

测试机器:MacBook Pro,芯片 M1 Pro 10核 CPU,内存 16GB,操作系统 MacOS Ventura 13.0.1

2000 名学生,每名学生连续访问10次抢课接口,抢票接口限流为每个学生每秒3次,同时抢容量为 80 的课程的测试结果如下:

实测没有超抢或数据库与缓存的数据不一致性问题

2273447026857f012fb522e9b644cc31

TODO

使用GUI方式启动JMeter,在运行线程较多的测试时,会造成内存和CPU的大量消耗,导致客户机卡死,JMeter也会提示不要在GUI模式下进行压测。实测当线程数达到5000,loop超过1或者线程数达到10000时,单个loop时就会报错卡死。这里有时间研究一下非GUI模式下的压测表现