学生管理系统 - 考试试卷存储方案
前言
本文是学生管理系统的考试试卷存储架构的详细设计文档,用于指导考试试卷存储的后续开发、测试和运维。
1. 业务背景
学生管理系统需要对学校提供机考考试业务的支持。考试业务需要能够支持上千万考生同时在线获取考试内容,保存作答内容,限制考生的作答时间。并能够在考试结束后,提供完整的试卷信息和作答信息,给相关的批改老师,用于试卷批改。
为了保证上千万级的学生在线考试的稳定性和可行性,需要设计一个试卷存储系统,用于支撑大量数据的读取和存储。
综上所述,系统黑盒图结构描述如下:
2. 约束和限制
1. 系统需要支持动态扩缩容:上千万考生同时在线考试,只会发生在期末考试,或者联考等大型考试场景。平时机考的使用频率不高。设计需要考虑平时的运营维护成本。
2. 存储采用 Redis sentinel
3. 总体架构
根据需要提供的业务,试卷存储系统以下三个部分,存储查询服务(对外简称存储系统),机考存储,归档存储。白盒图结构描述如下:
存储查询服务:对外提供试卷导入、试卷查询的功能。对内管理试卷的存储方式、存储位置
机考存储:用于存储当前考试周的考试试卷信息,需要提供高性能高可用的读服务。
归档存储:用于存储所有的考试试卷。需要支撑海量存储。
3.1 架构分析
试卷存储系统的主要业务有两个: 存储试卷内容、提供试卷信息。
根据需求分析,系统有 1000 万名学生,参考 2019 年高校数据,平均每个学校 1.5w 学生,推算系统大约需要服务 700 所学校,师生平均比例为 1:17.5,教师用户数 57 万人。
假设 1:老师需要在考试前一个星期内,将试卷上传到系统里,按每个老师需要负责出一份考试卷。老师上传试卷的时间为工作日的工作时间。试卷的题目量较大,老师不可能一次编辑完成,平均每个老师要修改 3 次完成试卷内容
存储试卷 TPS = 57 万 * 3 / 5 (天) / 8 (小时) / 3600 = 12
假设 2:1000 万学生,每个学生平均 20 门课,考试采用机考,试卷和试题的信息存储在系统内。考试集中在上午 4 小时和下午 4 小时,且请求试卷集中在考试开始的前 1 分钟,周末不考试。则考试周时:
考卷信息 QPS = 1000 万 * 20(课)/ 20(周末不考试) / 4(每天 4 堂考试)/ 1 分钟 = 250 万请求/分钟 ≈ 5 万/每秒。
假设 3:老师在批改试卷时,主要是需要调取学生的作答数据,老师熟悉自己出的卷子,在批改过程中不需要频繁调阅试卷信息。因此批改时对试卷的读取性能在这里不做分析
综上所诉,试卷存储系统的主要复杂度,来自千万数量级的学生同时在线考试时,对试卷信息的读取。需要做到高性能高可用。考试有时间限制,而且所有学生同时开始考试,系统需要支持至少 5 万/每秒的试卷查询,同时也需要保证,在整个考试过程中,试卷信息是可访问的。
假设 4:一次考试周,产生 57 万张试卷,每年 2 次考试周,一年产生 114 万试卷。如果系统使用 10 年,则大约有 1k 万条试卷数据。每张试卷的信息按 1M 算,大约 1 年 1T 的数据。根据适当原则,存储在几年内都不会是系统的瓶颈。
3.2 总体架构
系统通过路由动态分配来实现负载均衡。Router 将请求均匀分配到试卷查询服务器。
对于机考业务,试卷查询服务通过 Redis sentinel 访问 Redis 存储查询试卷信息
对于机考业务,选用一主多备,集群选举模式。
对于归档试卷业务,试卷查询服务通过访问 Redis 分片,到指定的 db 查询试卷信息
对与归档试卷存储,选用一主一备的模式,每个分片备份一个副本。可以根据存储的试卷量增长 ,逐年递增存储设备。
4. 详细设计
4.1 核心功能
1)试卷导入流程
涉及角色与规则:
老师:老师时导入流程的操作用户,将试卷信息上传到教师系统
教师系统:处理老师上传的试卷,试卷存储系统返回的试卷 uuid。通过教师系统,将 uuid 和对应的老师、考试关联起来。
存储系统:管理机考存储、归档存储。根据试卷的归属,分配试卷到对应的存储分片。
机考存储:机考时,考试系统通过存储,访问机考存储获得试卷信息。在考试结束后,机考存储的信息会被清空。
归档存储:所有的试卷,按考试年份分配到归档存储的分片中。
2) 机考试卷读取流程
涉及角色与规则:
学生:机考业务的操作用户,通过考试系统,请求开始考试
机考系统:根据学生找到课程系统里,学生当前对应的考试信息,通过 uuid 向存储系统获取考试试卷
存储系统:根据试卷的 uuid,sharding 计算对应的机考存储 db,查找试卷
机考存储:使用 redis,用 uuid 为 hashtable 的 key 存储试卷信息
3)归档试卷读取流程
以老师访问已结束的考试试卷为例,学生的访问流程类似
涉及角色与规则:
老师:课程系统的用户,通过课程系统,查询已结束的考试。
课程系统:根据老师指定要查看的考试,从课程系统的数据库内,找到对应试卷的基础信息,如:学校、考试年份、考试 uuid。并向试卷存储系统发起获取归档试卷信息的请求
存储系统:试卷存储系统,根据请求的试卷基本信息,根据年份查找对应 db,然后根据请求数据里试卷的基本信息,拼接 key(学校+考试年份+uuid),根据拼接的 key 查找信息并返回
归档存储:redis 存储,用 string 格式的 key,归档存储试卷信息
4.2 关键设计
1)考试试卷存储方式
使用 sharding 算法,将当期(一个考试周内)的试卷根据 uuid 均匀分布到 redis 的 10 个 db 中。根据 3.1 的分析,考试周期间,系统需要提供 57 万分试卷,每秒 5 万次的高可用查询读取。每个 db 存储约 6w 条试卷信息。
key 格式: HashTable,生成全局试卷唯一编号
存储内容:试卷信息,包括试卷内的题目信息,完整存储。其它系统在请求获取试卷信息时,直接将整条试卷信息完整返回即可,不需分步查询。
存储格式:使用 proto 定义存储格式。考虑到考试时,流量也会成为系统的瓶颈,因此选用占用字节流小的存储格式。
2)归档试卷存储
结束考试后,试卷归档到低频访问的 redis 集群中。考虑到 Redis 的读写特点,试卷以考试年份为单位进行存储,同一个年份的数据,必须在同一个 db 中,通过 key 里的学校 id 识别属于哪个学校。由存储系统维护年份和 db 的对应关系。如果归档的存储的数据预期新的一年会超过 redis 的承载量,就增加新的 db。如果因为运营变化,需要切换年份对应的 db,需要将历史数据统一导入到新的 db 中。
老师在导入试卷时,同时触发归档试卷备份。即归档备份,在试卷导入系统时已完成。
key 格式: String, 学校 id + 考试年份 + 唯一编号
存储内容:同考试试卷存储,存储完整信息
存储格式:同上
3)试卷查询
试卷查询主要通过其它系统触发。试卷的全局唯一编号冗余在老师系统和学生系统中。
客户端获得试卷数据后,通过预定义好的 proto 解析内容格式。
评论