选课系统设计

1. Redis 存储时间管理

  • 存储开始时间和结束时间。
  • 通过定时任务在到点时删除这两个 key,以开放选课。

2. Redis 缓存课堂信息

  • 存储课堂信息(包括 ID、课程 ID、教师 ID、课堂限选人数)。
  • 通过 Redisson 设置课堂信息的分布式信号量:
    • Key:课堂 ID
    • Value:限选人数

3. 学生选课流程

  • 学生开始并发选课,点击选课按钮。
    • 情况 1:如果 Redis 中 jwxt:xkmk:xkxx 没有数据,则插入数据(假设学生 ID 为 1001,课堂 ID 为 001):

      • 执行命令:SET jwxt:xkmk:xkxx 1001-001 1
      • 同时减少信号量数据:boolean f = redisson.getSemaphore("001").tryAcquire(1);
    • 情况 2:如果有数据但为 0,则修改数据:

      • 执行命令:SET jwxt:xkmk:xkxx 1001-001 1
      • 同时减少信号量数据:boolean f = redisson.getSemaphore("001").tryAcquire(1);
    • 情况 3:如果有数据且为 1,则返回信息:已选该课程(前端应转换为退选按钮,但可能因网络抖动出现此情况)。

4. 学生退选流程

  • 如果有数据且为 1,则插入数据(假设学生 ID 为 1001,课堂 ID 为 001):

    • 执行命令:SET jwxt:xkmk:xkxx 1001-001 0
    • 同时增加信号量数据:boolean f = redisson.getSemaphore("001").release();
    • 为防止超出初始值,使用 Lua 脚本进行增加判断。
  • 如果有数据但为 0,则提示:该课程已退选。

  • 如果 Redis 中 jwxt:xkmk:xkxx 中没有对应数据,则返回信息:该课程未选。

5. 数据持久化与消息队列

  • 将需要直接持久化到 MySQL 的数据(如选课单和退选单)放入消息队列(MQ)中。

    • 示例:当用户 1001 先选了课堂 001(状态为 1),然后又点击退选(状态为 0),这些操作先在 Redis 中修改,同时将这两个操作放入 MQ。
  • 根据用户 ID 1001 进行队列取模,确保每个用户的所有操作都在一个队列上,保证顺序消费。

  • 消费者端加上分布式锁,锁粒度为用户课堂级别:

    • Key:user_lock_1001_101
    • 确保每个用户在操作一个课堂的选课退选时上锁,但不影响其他操作,保证顺序消费和高并发。
  • 为了保证幂等性,防止消息重试导致的重复消费,添加 Redis 锁:

    • Key:数据的唯一 ID
    • 设置过期时间为 10 秒。
    • 多个线程尝试持有锁,成功则执行操作,失败则放弃执行。

6. Redis 数据结构

  • String: 存储开始时间和结束时间

    • Key: kssj
    • Value: 2024-10-03
  • Hash: 存储课堂信息

    • Key: jwxt:xkmk:ktxx
    • Field: 课堂 ID
    • Value: 课堂信息 + 课程 ID + 教师 ID 的 JSON
  • Hash: 存储选课信息

    • Key: jwxt:xkmk:xkxx
    • Field: 课堂 ID + 学生 ID
    • Value: 0/1(0 代表退选,1 代表已选)
  • String: 存储课堂信息分布式信号量

    • Key: 课程班随机码
    • Value: 限选人数