高校选课系统设计(一)

目录

一、业务简介

二、人才培养方案(教学计划)设计

三、开课和选课前置操作

四、选课功能实现与技术选型

1.传统模式(直接操作数据库)

2.参考“秒杀”系统的设计

3.Redis + Lua解决原子性问题


一、业务简介

        一般采用学分制的教学班比较灵活,一个教学班里可能会有不同年级,不同专业甚至不同课程性质(同一个教学班课程对一个同学是必修,可能对另外一个是选修)的学生,而采用学年制的可能就没这么复杂,一般是一个行政班就在一个教学班上课。但是不管是采用学分制还是学年制,归根到底还是要有教学班和学生的数据关系。

二、人才培养方案(教学计划)设计

        培养方案设计应该考虑扩展性,针对学生不同的培养方式要有不同的培养方案。设计表时要预留有扩展的字段。 建议人才培养方案结构设计得不要太复杂,数据表不要太多,不然后面关联查询表太多,查询慢,可考虑冗余一些字段。 可以这样设计:培养方案下面直接关联课程(培养方案课程),课程有课程性质(必修已修这样的)和开课单位属性。培养方案里每个课程性质的课程的总学分可以比毕业要求的该课程性质的学分要高一些,这样学生只需要选择里面的一部分课程满足毕业要求即可。同时最好培养方案下面的课程不要挂太多,如果太多会导致后面学生和教学班的关系数据量很大。

三、开课和选课前置操作

        开课时就是把培养方案课程生成为教学班数据,根据业务情况,如果有需要可以给教学班设置面向对象(就是设置这个教学班能被哪些年级或者哪些专业或者其他条件的学生能选),不管有多少业务,也不管业务多复杂,最后就要生成一个学生和教学班的关系表,也就是每个学生能选到哪些教学班。这个学生和教学班关系数据不必要实时生成,只要在任何会影响这个关系数据的地方发送消息到消息队列即可,表示需要同步该学生的教学班数据。例如,有个学生转专业了,或者留级了,这样会导致学生的培养方案会变,该学生关联的教学班也会变。在执行该学生转专业或者留级操作之后发送一条消息到消息队列,消息队列慢慢消费,重新根据这个学生的最新学籍数据来生成该学生相关的教学班数据。

        在选课之前,学生和教学班关系数据应该已经都有了,这些数据就是选课的源数据。选课过程就是学生从该学生的学生教学班关系表选中一部分课程,选课结果就是生成学生和已选教学班结果表,可以看做是往教学班里塞学生。 考虑到并发量问题,选课可以分批进行,选课批次绑定教学班,指定每个选课批次只能选哪些教学班,选课批次有时间限制。这样就可以起到分批限流的作用。

四、选课功能实现与技术选型

        当已有学生和教学班关系数据时,选课就变得比较简单了,只要在页面展示学生能选到的教学班,然后学生选自己想要的教学班,选中确定后后台直接保存选课结果即可。其实现功能方式可以有好几种。

1.传统模式(直接操作数据库)

        直接读取数据库中学生和教学班关系表数据,根据当前选课批次查询该选课批次的且是该学生自己关联的教学班数据供学生选课。学生在前端页面点击确认选课,后台保存选课即可。

        但是实际高校的选课可能要满足2~3万学生同时在线选课的要求,本身选课就会有一些查询校验操作,例如选择的当前课程是否和学生已选课程上课时间冲突,某个课程性质的课只能选几门或者多少学分,已选课程是否已超过学生当前学期的学分上限,选择的教学班是否还有余量等等业务校验。最重要的是还要保证选教学班的人数不能超过课容量(类似“超卖”问题)。当选课有这些复杂业务查询校验的时候,并发量一高,可能数据库就支撑不了。如果数据库能顶得住这些压力,那可以采用这种方式。如果数据库支撑不住就要开始考虑其他的技术实现。

2.参考“秒杀”系统的设计

        “秒杀”系统的设计建议去看尚硅谷的谷粒商城项目视频(项目视频资料都是开源的)。

        参考商城的“秒杀”系统设计,把需要的数据全部都提前存入到redis,然后选课时都是从redis读取和修改数据。选课要做成一个单独的应用,消费选课队列的应用也是单独的应用。

        大致的步骤为:①选课前按选课批次把数据存入redis,把选课时需要用到的数据都存入redis;②学生在选课界面看到的数据都是当前时间在选课批次的选课时间范围之内的,全部从redis查询数据;③选课时后台的查询数据校验也都是从redis里查询数据,确定选课的选课记录也要存在redis里;④选课成功后发送数据给消息队列,消息队列排队消费,选课结果存到数据库;

        补充说明:①存在redis里的数据要设计好数据类型,比如教学班数据转json后按hash存在redis,为的是方便可以批量获取减少io次数,教学班的上课节次和学生的上课节次可以按set存,set有方法可以判断是否有交集数据,学生的学分上限数据也可以按hash存,方便获取单个属性;②每个教学班课程数据都应该设置个随机码,以防学生在选课之前就开始刷选课数据;③教学班余量可以设计成信号量;④要存每个学生的选课列表到redis,且要有选课状态字段,这样才知道哪些教学班课程是已选未选,为的是不用从数据库查询是否已选;⑤消息队列(rabbitMq)要设置开启发送端消息抵达Broker确认,开启发送端消息抵达Queue确认,开启消费端手动确认,要保证数据不丢失;⑥redis要做cluster集群或者sentinel模式;

测试效果如下:

选课耗时大概35ms左右

 退选的逻辑这里不再多详细,逻辑大概与选课反着来就是了。耗时差不多。

 问题:这种选课方式虽然全程不读取数据库了,但是选课时多次写redis数据,且还要发送消息队列数据,怎么保证这些redis操作的原子性?如果前面的和redis的IO操作成功了,但是后面又失败了,这怎么处理?

3.Redis + Lua解决原子性问题

        优化上面的架构,把选课时所有的redis操作都写到Lua脚本里,Lua脚本执行是原子性的。把之前的选课代码按Lua语法重新写一遍即可。

        这里注意一些问题:①可以把Lua脚本里的代码看成是一个方法,最后执行完后要返回数据,可以封装成一个对象(Lua里叫table),可以有成功失败标记,失败错误信息和成功返回的要发送给mq的数据(封装成一个table),执行成功后返回string,JSON转成对象即可拿到结果;②如果用Lua脚本那redis集群不用用cluster,因为一个Lua脚本的所有redis操作的key只能在一个hash槽,可以考虑用sentinel模式;③Lua脚本可能会有一些坑,做的时候要多测试和做好数据校验,Lua的一些坑参照这个:redis使用lua脚本回滚失败的原因_CRUD的W的博客-CSDN博客_lua redis 回滚

改Lua脚本后测试效果如下:

时间不超过10ms,快了近10倍。

        所以采用这种模式,只要redis服务器性能够,2~3万学生同时在线选课的压力应该是没问题。

        但这种模式还是可以优化的,既然选课应用没有用到事务型数据库,那可以引入Spring Reactor响应式编程,可以将选课,退选和查询可选等进行改造,这个留下次再改造试试。

    

        
 

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值