# xuanke **Repository Path**: ygp2015/xuanke ## Basic Information - **Project Name**: xuanke - **Description**: 基于SpringBoot的高并发选课系统 - **Primary Language**: Unknown - **License**: BSD-3-Clause - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 1 - **Created**: 2023-10-22 - **Last Updated**: 2025-03-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于SpringBoot的高并发选课系统 ## 项目简介 本项目主要解决在高校选课场景下,保证选课系统在大量读写压力下不宕机,以及选课时尽可能提高选课QPS,给学生一个良好的选课体验,完成上述功能同时保证选课安全 ## 技术选型 前端:Bootstrap、JQuery、Thymeleaf 后端:SpringBoot、Shiro、JPA、Caffeine 中间件:Redis、RabbitMQ、Druid 数据库:MySQL ## 优化思路 ### 页面查看 解决思路:从Redis缓存中查看数据,减少数据库访问 从数据可见性角度来讲,分为对所有人可见的公有信息,和只有自己可见的私有信息 ### 登陆 1. 学生可能多次登陆系统,第一次登陆时将学生信息加载进Redis,减少后续登陆时对数据库的访问 2. 通过单例模式构建全局唯一类,根据sessionID保存学生学号,供后续使用 ### 选课 **_此功能为整个系统的重点优化之处,_** 主要分为两大步骤,选课安全验证和执行选课操作 #### **选课安全验证** 1. IP限流,每分钟可以访问三次,与学号绑定,通过Redis实现 2. 判断是否存在该课程,从Redis中查看 3. 判断是否在规定选课时限范围内,从Redis中查看 4. 若满足上述步骤,根据课程号生成其md5值,暴露秒杀地址 5. 执行选课操作时验证秒杀地址是否正确 #### 执行选课操作 1. 通过本地标记判断是否有余量,若有,执行后续 2. 判断是否重选,通过查看Redis是否有对应缓存来实现 3. 判断上课时间是否冲突,构造冲突判断算法,遍历Redis中已选课程进行验证 4. 库存预减,当库存为0时,将该授课计划添加到本地缓存中,本地缓存通过Caffeine构建 5. 选课请求压入MQ,异步执行,流量削峰,消费端消费选课信息,将结果写入数据库,结果写入数据库这一步采用事务机制,先插入结果,后减余量,减少事务期间锁持有时间,优化数据库读写性能 6. 返回执行成功标识,但结果需要到选课查询页面确认 #### 预加载 通过上述代码所示,Spring在生成Bean初期,会将选课所需要的相关数据加载到Redis中 #### 超选 ### 退课 1. 到Redis中查看是否存在该条选课记录 2. 删除该条选课记录,对应授课计划余量加一(事务,减少锁持有时间) 3. 修改Redis中对应数据,余量加一,删除掉该条选课记录 4. 返回退课成功标识 ### 其他优化点 **安全相关:** 存储在数据库中的密码和选课链接都经过了MD5加密,对应MD5值的生成经过了两次加盐 **数据库操作:** 引入Druid数据库连接池,提升对数据库的操作性能 ## 压力测试 测试内容:选课QPS 测试技术:Jmeter 测试环境:MacBook Pro2018,JDK1.8,Jmeter 5.3 测试计划:理想状态下(余量充足,不存在重复选课,上课时间不冲突等),同时执行5000个线程(模仿5000个同学),选同一节课,看选课执行情况 测试方案: 1. 利用mysql存储过程生成5000个学生学号,并将数据导出为cvs格式,供Jmeter使用 2. 使用Jmeter图形化界面生成测试方案,验证测试方案可行性 3. 使用Jmeter命令行压测(图形化界面仅用来做验证,并不适合高负载压测),执行5次,查看结果 ### 压测结果说明 多次压测后得出来的平均选课qps为500出头,读页面的平均选课qps为900多