高并发指的是在单位时间内,瞬时流量激增,系统需要同时处理大量并行的请求或操作。这种情况通常出现在面向大量用户或服务的分布式系统中,尤其是当用户请求高度集中时,比如促销活动、秒杀活动、注册抢课、热点事件、定时任务调度等。
在高并发发生时,系统可能存在以下问题:
1.系统性能维度
2.用户行为维度
3.数据处理维度
为了应对高并发带来的压力,在高并发场景下,系统设计和优化可以从以下几个维度进行调整:
通过上述维度的策略实施,可以显著提升系统在高并发环境下的性能和稳定性。然而,每个系统的具体场景和需求都有所不同,因此在实施优化时需要根据实际情况进行定制化的调整。
在大学抢课场景,课程的人数限制为30个学生,系统面临的主要问题包括:
以下是使用Spring Boot和Redis实现大学抢课逻辑的示例代码
CourseController.java - REST 控制器用于处理课程注册请求:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/courses") // 定义API的基础路由
public class CourseController {
@Autowired
private CourseService courseService; // 注入课程服务类
@PostMapping("/{courseId}/enroll") // 定义POST请求,用于抢课操作
public ResponseEntity<?> enrollStudent(@PathVariable("courseId") String courseId, // 课程ID作为路径参数
@RequestParam("studentId") String studentId) { // 学生ID作为请求参数
boolean result = courseService.enroll(courseId, studentId); // 调用服务类的方法进行抢课
if (result) {
return ResponseEntity.ok("Enrollment successful!"); // 如果成功,返回成功响应
} else {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Course is full."); // 如果失败,返回服务不可用响应
}
}
}
CourseService.java - 服务类使用Redis进行分布式锁控制:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
@Service
public class CourseService {
@Autowired
private StringRedisTemplate redisTemplate; // 注入Redis字符串模板类
@Autowired
private EnrollmentRepository enrollmentRepository; // 注入选课记录的持久层接口
private static final String LOCK_SCRIPT = // 定义Lua脚本用于获取锁
"if redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) == 1 then return 1 else return 0 end";
public boolean enroll(String courseId, String studentId) {
String lockKey = "course:" + courseId + ":lock"; // 定义锁的key
String studentKey = "course:" + courseId + ":student:" + studentId; // 定义学生的key
// 使用Redis的Lua脚本原子地尝试获取锁,使用随机值和1000ms超时
Boolean acquiredLock = redisTemplate.execute(new DefaultRedisScript(LOCK_SCRIPT),
Collections.singletonList(lockKey),
studentId,
String.valueOf(1000L));
if (Boolean.TRUE.equals(acquiredLock)) {
try {
// 检查学生是否已经选过这门课程
if (redisTemplate.opsForSet().isMember(studentKey, studentId)) {
return false;
}
// 检查剩余座位数
Integer remainingSeats = redisTemplate.opsForValue().increment("course:" + courseId + ":seats", -1);
if (remainingSeats >= 0) {
// 选课成功,将学生添加到选课集合中
redisTemplate.opsForSet().add(studentKey, studentId);
// 保存选课记录
Enrollment enrollment = new Enrollment(studentId, courseId);
enrollmentRepository.save(enrollment);
return true;
} else {
// 恢复座位数,因为课程已满
redisTemplate.opsForValue().increment("course:" + courseId + ":seats", 1);
return false;
}
} finally {
// 总是在finally块中释放锁,以防止锁泄露
redisTemplate.delete(lockKey);
}
} else {
return false;
}
}
}
EnrollmentRepository.java - 持久层接口用于管理选课记录:
import org.springframework.data.jpa.repository.JpaRepository;
public interface EnrollmentRepository extends JpaRepository<Enrollment, Long> {
// JPA/JDBC方法用于管理选课记录
}
在CourseService
中,我们使用Lua脚本来尝试获取课程的锁。如果锁被成功获取(acquiredLock
为true
),我们进一步检查学生是否已经选过这门课程。如果没有,我们减少座位数,并且如果座位仍然可用,我们将学生添加到选课集合中并保存选课记录。如果课程已满或者学生已经选过这门课程,我们释放锁并返回false
。
请注意,上述代码知识一个思路演示,在生产系统中,还需要处理各种边缘情况和潜在的异常。可能还需要适当配置StringRedisTemplate
和EnrollmentRepository
,包括在Spring Boot应用程序中设置必要的依赖和注解。
此外,用于锁定和跟踪学生请求的Redis键需要精心设计,以避免冲突,并确保它们可以被轻松管理和在不再使用后清理。