Redission分布式锁 - 抢课系统

使用Redission分布式锁与Kafka消息队列,实现学生抢课系统(高并发秒杀场景)。

一、思路

1.为频繁访问的信息设置缓存

后台设置抢课任务,配置抢课时间范围。一般情况下,用户会在抢课时间开始前至抢课前期一段的时间集中访问系统。

(1)登陆

系统在登陆时会查询数据库返回用户信息,所以需要对登陆接口进行改造,将用户信息(比如姓名、年级、班级、联系方式等)保存至缓存中。当用户信息发生变更时,才将信息从缓存中清除。
用户登陆页面

(2)课程任务信息

用户登陆进入系统后,进入到抢课任务模块对应的抢课任务中,进行课程选择。在抢课任务期间,用户会为频繁的访问该模块信息(抢课任务、关联课程、课程库存等),后台在进行信息校验时也会用到以上数据。
抢课任务详情
选课列表

(3)用户抢课记录

在抢课期间,用户抢课退课操作和查询比较频繁,可将该数据保存至缓存,方便查询。
已选课程列表

2.消息队列和分布式锁

为Kafka的抢课消息队列设置了一个主题五个分区,可处理大量并发抢课消息的情况。然而在每个分区内消息是有序的;不同分区中的消息无序,会出现多个进程同时进行的情况,而多个进程必须以互斥的方式独占共享资源(课程库存)。

为保证每一课程库存操作的独占性,为课程库存设置了锁定缓存(LockKey)数量缓存(StockKey)
注意:当消息抢夺到锁定缓存时,才可对数量缓存进行扣除(-1)的操作。

(1)抢课消息队列

对用户抢课数据进行校验(是否符合年级、已选课程日期冲突炖、课程班级人数限制、课程库存余量等),校验通过后将请求数据发送至Kafka抢课消息队列。

(2)锁定缓存抢夺

此处使用了Redission分布式锁,当同时有多个用户发送同一课程的消息时,消费端接收到消息在5秒内尝试获取锁定缓存(LockKey),若获取成功加锁30秒,否则失效。

(3)数量缓存操作

获取锁定缓存(LockKey)成功后,查询数量缓存(StockKey)。此时需要对各项业务数据再次进行校验,因为在数据进入消息队列前进程仍是并发的,可能会出现数据已变动的情况。在满足抢课条件后,取出数量缓存(StockKey)库存进行数量减一的操作,操作完毕最终释放锁定缓存(LockKey)

二、具体流程

1.抢课任务设置

(1)后台管理人员配置抢课任务信息(开始时间、结束时间、课程、课程时间等)。
(2)课程任务正式发布,保存信息(任务、关联课程、自定义课程可选人数等)到缓存;当任务取消发布时,需要将对应的缓存删除。

2.用户抢课

(1)首次登陆查询用户信息,并保存至缓存。
(2)进入抢课任务信息,查看可选课程列表。
(3)点击抢课按钮发送请求。
(4)选课成功:保存学生对应任务已选课程集合到缓存;更新任务课程库存数量等缓存信息。
(5)退课:更新学生已选课程缓存;清除任务课程库存数量缓存。

三、实体类表

此处列举部分核心数据库表设计。

1.课程表

id 课程名称 课程编号 课程教室 课程简介 教师id 教师名称
1 舞蹈兴趣班 C0001 1号楼6楼舞蹈教室 面向0基础学生 10001 王老师
2 画图兴趣班 C0002 1号楼2楼美术教室 面向0基础学生 10002 李老师
3 音乐兴趣班 C0003 3号楼1楼音乐教室 面向0基础学生 10003 陈老师

2.选课任务表

id 任务名称 可选年级id集合 可选班级id集合 学生可选课程总数 开始时间 结束时间 发布状态 发布时间 任务状态
1 2023年秋季选课 2019级id,2020级id 2020级-1班id,2020级-2班id,2019级-3班id 2 2023-08-01 09:00:00 2023-08-07 20:00:00 已发布 2023-07-20 09:00:00 已结束
2 2024年春季选课 2019级id,2020级id 3 2024-01-30 09:00:00 2024-01-07 20:00:00 已发布 2024-01-20 09:00:00 进行中

3.选课任务课程关联表

id 任务id 课程id 课程可选人数 开始日期 结束日期 课程表json
1 1 1 50 2023-09-20 2023-12-30 [{“name”:“周一”, “section”:[{“name”:“第5节”,“state”:“1”}]},{“name”:“周二”, “section”:[{“name”:“第3节”,“state”:“1”}]}]
2 1 2 50 2023-09-20 2023-12-30 [{“name”:“周二”, “section”:[{“name”:“第3节”,“state”:“1”}]},{“name”:“周四”, “section”:[{“name”:“第5节”,“state”:“1”}]}]
3 1 3 50 2023-09-20 2023-12-30 [{“name”:“周三”, “section”:[{“name”:“第5节”,“state”:“1”}]},{“name”:“周五”, “section”:[{“name”:“第5节”,“state”:“1”}]}]
4 2 1 50 2024-03-01 2024-05-30 [{“name”:“周一”, “section”:[{“name”:“第5节”,“state”:“1”}]},{“name”:“周二”, “section”:[{“name”:“第3节”,“state”:“1”}]}]
5 2 2 50 2024-03-01 2024-05-30 [{“name”:“周二”, “section”:[{“name”:“第3节”,“state”:“1”}]},{“name”:“周四”, “section”:[{“name”:“第5节”,“state”:“1”}]}]
6 3 3 50 2024-03-01 2024-05-30 [{“name”:“周三”, “section”:[{“name”:“第5节”,“state”:“1”}]},{“name”:“周五”, “section”:[{“name”:“第5节”,“state”:“1”}]}]

4.学生选课关联表

id 任务id 课程id 学生id 选课状态 老师帮选表示 选课时间
1 1 1 1 已选课 2023-08-01 09:01:00
2 1 1 2 已取消 2023-08-01 09:01:01

四、核心代码

1.消费端

(1)Kafka配置:主题、分区初始化
(2)用户抢课数据初步通过校验,发送到消息队列中的处理逻辑。

@Component
@Slf4j
public class KafkaConsumer {
   
/**
     * 初始化学生选课主题分区 5个
     * 通过注入一个 NewTopic 类型的 Bean 来创建 topic,如果 topic 已存在,则会忽略。
     */
    @Bean
    public NewTopic courseSelectionBatchTopic() {
   
        log.info("创建学生选课主题courseSelectionBatchTopic : szxy_oa_course_selection_student_add_topic,分区:5,副本数:1 >>>>>>>>>>>>>>>>>>>>>>>>>>>>> ");
        NewTopic newTopic = new NewTopic(OaConstant.COURSE_SELECTION_STUDENT_ADD_TOPIC, 5, (short) 1);
        log.info("newTopic:topicName:{},分区: {} >>>>>>>>>>>>>>>>>>>>>>>>>>>> ", newTopic.name(), newTopic.numPartitions());
        return newTopic;
    }
 /**
     
     *添加学生选课主题消息
     */
    @KafkaListener(topics = OaConstant.COURSE_SELECTION_STUDENT_ADD_TOPIC,groupId = KafkaProducer.TOPIC_GROUP)
    public void courseSelectionStudentAddMsg(ConsumerRecord<?, ?> record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
   
        log.info("COURSE_SELECTION_STUDENT_ADD_TOPIC-学生选课队列消费端 topic:{}, 收到消息>>>>>>>>>>>>>>>>>", topic);
        try {
   
            Optional message = Optional.ofNullable(record.value());
            if (message.isPresent()) {
   
                Object msg = message.get();

                // 先判断消息是否已经已经消费过,5s
                String fullKey2 = redisLockUtil.getFullKey(COURSE_SELECTION_STUDENT_CONSUME_LOCK_PREFIX , String.valueOf(msg))
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值