1. 系统整体设计思路
作为一款面向高校教务管理的在线选课系统,我们采用SpringBoot作为基础框架,主要解决传统选课系统面临的三大痛点:高并发场景下的系统稳定性、多角色权限的精细化管理、以及教务流程的数字化升级。系统设计遵循"模块化、高可用、易扩展"三大原则,通过分层架构实现业务逻辑与技术实现的解耦。
1.1 技术选型考量
后端选择SpringBoot 2.7.x而非最新版,主要基于企业级应用的稳定性考量。该版本长期支持(LTS)到2025年,与Spring Security 5.7.x、MyBatis-Plus 3.5.x等组件兼容性经过充分验证。数据库选用MySQL 8.0而非5.7,因其原生支持JSON字段类型和更好的并发性能,适合存储动态扩展的课程属性。
注意:生产环境建议使用MySQL 8.0.28以上版本,该版本修复了多个可能导致死锁的BUG
缓存层采用Redis 6.x集群而非单节点部署,通过分片存储解决选课高峰期热点数据访问问题。实测表明,在1000并发选课请求下,Redis集群可将数据库QPS从1200降低到200以下。
1.2 架构设计图解
系统采用经典的三层架构:
code复制表示层(Vue.js/Thymeleaf)
↑↓ HTTP/HTTPS
业务逻辑层(SpringBoot)
↑↓ JDBC/MyBatis
数据持久层(MySQL+Redis)
关键设计决策:
- 前后端分离:通过RESTful API交互,便于多终端适配
- 无状态认证:JWT替代Session,减轻服务端内存压力
- 异步日志:通过Logstash将操作日志异步写入ELK
2. 核心模块实现细节
2.1 选课业务逻辑实现
选课核心流程包含三个关键步骤:
java复制// 伪代码示例
public Result selectCourse(Long studentId, Long courseId) {
// 1. 校验选课资格
if (!courseService.checkEligibility(studentId, courseId)) {
return Result.fail("不满足选课条件");
}
// 2. 分布式锁防超卖
String lockKey = "course_lock:" + courseId;
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 3. 事务处理
return transactionTemplate.execute(status -> {
if (courseQuotaService.reduceQuota(courseId)) {
studentCourseService.addRecord(studentId, courseId);
return Result.success();
}
status.setRollbackOnly();
return Result.fail("课程已满");
});
}
} finally {
lock.unlock();
}
}
2.1.1 并发控制方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 数据库乐观锁 | 低并发场景 | 实现简单 | 高并发时重试次数多 |
| Redis分布式锁 | 中高并发场景 | 性能较好 | 需处理锁续期问题 |
| 令牌桶限流 | 超高并发场景 | 保护下游系统 | 存在请求丢弃 |
我们最终选择Redis分布式锁+本地缓存的组合方案,在测试环境可稳定支持5000+TPS的选课请求。
2.2 权限系统设计
基于RBAC模型进行扩展,实现动态权限控制:
sql复制CREATE TABLE `sys_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`permission_key` varchar(50) NOT NULL COMMENT '权限标识如course:select',
`permission_name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- 角色权限关联表
CREATE TABLE `sys_role_permission` (
`role_id` bigint NOT NULL,
`permission_id` bigint NOT NULL,
PRIMARY KEY (`role_id`,`permission_id`)
) ENGINE=InnoDB;
在Spring Security中的配置要点:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/course/**").hasAuthority("course:manage")
.antMatchers("/api/student/**").hasAnyRole("STUDENT", "ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
3. 性能优化实战
3.1 数据库优化方案
- 分表策略:按学年将选课记录拆分到不同物理表,如
student_course_2023、student_course_2024 - 索引设计:对高频查询字段建立组合索引
sql复制ALTER TABLE course ADD INDEX idx_teacher_time (teacher_id, class_time); - 查询优化:使用MyBatis-Plus的QueryWrapper避免N+1查询
java复制new QueryWrapper<Course>() .select("id", "name", "teacher_id") .eq("status", 1) .orderByAsc("credit");
3.2 缓存策略实施
采用多级缓存架构:
- 一级缓存:本地Caffeine(最大10000条,过期时间5分钟)
- 二级缓存:Redis集群(过期时间30分钟)
- 缓存击穿防护:使用BloomFilter过滤无效课程ID
缓存更新策略对比表:
| 策略 | 一致性 | 实现复杂度 | 适用场景 |
|---|---|---|---|
| 定时过期 | 弱 | 简单 | 低频更新数据 |
| 主动更新 | 强 | 复杂 | 财务等关键数据 |
| 延迟双删 | 较强 | 中等 | 大多数业务场景 |
4. 典型问题排查实录
4.1 选课超时问题
现象:高峰期出现选课接口响应超过10秒
排查步骤:
- 通过Arthas trace命令定位到Redis连接获取阻塞
- 检查连接池配置发现最大连接数仅为50
- 调整Jedis连接池参数:
properties复制spring.redis.jedis.pool.max-active=200 spring.redis.jedis.pool.max-wait=1000ms
4.2 内存泄漏问题
现象:服务运行24小时后出现OOM
分析过程:
- 使用MAT分析heap dump文件
- 发现未释放的Thymeleaf模板缓存
- 解决方案:
java复制@Bean public SpringTemplateEngine templateEngine() { SpringTemplateEngine engine = new SpringTemplateEngine(); engine.setEnableSpringELCompiler(true); engine.setTemplateResolver(templateResolver()); // 添加以下配置 engine.setCacheManager(new NoOpCacheManager()); return engine; }
5. 扩展设计建议
5.1 微服务改造方案
当单应用无法满足需求时,可拆分为:
- 用户服务
- 课程服务
- 选课服务
- 成绩服务
使用Spring Cloud Alibaba组件:
- Nacos服务发现
- Sentinel流量控制
- Seata分布式事务
5.2 监控体系建设
推荐监控组合:
- 基础监控:Prometheus + Grafana
- 链路追踪:SkyWalking
- 日志分析:ELK Stack
关键指标报警阈值:
- CPU使用率 > 70%持续5分钟
- 选课接口P99 > 500ms
- MySQL活跃连接数 > 80%
6. 开发环境配置指南
6.1 本地开发配置
- JDK推荐使用Amazon Corretto 11:
bash复制
brew tap corretto/corretto brew install corretto11 - IDEA必备插件:
- Lombok
- MyBatisX
- Arthas Idea
- 数据库初始化脚本:
sql复制CREATE DATABASE course_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
6.2 生产环境部署
Docker Compose示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
7. 项目演进路线
7.1 短期优化
- 引入WebSocket实现选课结果实时推送
- 添加课程评价模块
- 开发移动端适配页面
7.2 长期规划
- 对接学校统一身份认证
- 接入在线支付系统
- 建设大数据分析平台
在项目开发过程中,我们发现SpringBoot的自动配置机制虽然方便,但也容易掩盖一些底层细节。比如Redis的Lettuce连接池配置,默认情况下不会显示警告日志,直到连接耗尽才会抛出异常。建议在开发阶段就通过以下配置开启详细日志:
properties复制logging.level.io.lettuce.core=DEBUG
logging.level.org.springframework.data.redis=DEBUG
另一个实用技巧是使用SpringBoot的ConfigurationProperties进行参数校验,比直接在字段上加注解更易维护:
java复制@Validated
@ConfigurationProperties(prefix = "course")
public class CourseProperties {
@NotNull
private Integer maxSelectCount;
@AssertTrue
public boolean isMaxSelectCountValid() {
return maxSelectCount > 0 && maxSelectCount <= 10;
}
}