千万级学生管理系统的考试试卷存储方案
1. 估算性能需求
1.1. 多少考卷?
每门课程有 100 名学生
在校学生 1000 万,每学期 20 门课,每科一年 2 次考试
每学期考卷数量:1000 万 * 20(课)* 2(考试次数)/100 = 400 万
在校学生考卷数量:400 万 * 2(学期) * 3(只有前三年考试) = 2400 万
要配合学生答案的存储,查看答案时需要看到配套的考卷。
离校学生考卷数量:每年 800 万
1.2. 考卷大小
考卷 20 判断题,20 选择题,4 道大题
考卷大小:文字部分 2000,图片部分 1M
考卷存储量
本学期考卷文字部分:400 万 * 2000 = 8G
本学期考卷图片部分:400 万 * 1M = 4T
在校学生考卷文字部分:48G
在校学生考卷图片部分:24T
离校学生考卷文字部分:每年 16G
离校学生考卷图片部分:每年 8T
1.3. 请求量
假设学校的考试都安排在某一个月(考试月)的 20 天内(周末不考试),每天 4 堂考试,请求试卷集中在考试开始前 1 分钟,因此估算如下:
请求读取考卷:1000 万 * 20(课)/ 20(20 天考试)/ 4(每天 4 堂课)/ 1 分钟 = 5 万/每秒
假设老师出卷都安排在考试月前的一个月(出卷月)的 20 天内(周末不出卷),假设老师在本地设计完整个试卷后一次性提交。因此估算如下:
提交考卷请求:400 万(每学期考卷)/ 20 (天)/ 8 (小时)= 7/每秒
2. 选择存储系统
2.1. 存储架构分析
2.1.1. 本学期考卷文字部分
单机能否存储所有数据:是
单机能否支撑写性能:是
单机能否支撑读性能:否
是否需要自动切换:是
主从切换或者集群选举
2.1.2. 本学期考卷图片部分
单机能否存储所有数据:否
是否需要分区部署:否
分片架构
2.1.3. 在校学生考卷文字部分
单机能否存储所有数据:是
单机能否支撑写性能:是
单机能否支撑读性能:是
是否需要自动切换:否
主备复制
2.1.4. 在校学生考卷图片部分
单机能否存储所有数据:否
是否需要分区部署:否
分片架构
2.1.5. 离校学生考卷文字部分
单机能否存储所有数据:否
是否需要分区部署:否
分片架构
2.1.6. 离校学生考卷图片部分
单机能否存储所有数据:否
是否需要分区部署:否
分片架构
2.2. 存储架构图
本学期考卷文字部分使用 Redis sentinel
在校学生和离校学生考卷文字部分使用 HBase 集群
所有考卷图片部分(本学期、在校学生、离校学生)使用 HBase 集群
3. 设计存储方案
3.1. 本学期考卷文字部分
3.1.1. 数据结构设计
key: "tp:" + 学校 ID + ":" + 考试 ID
使用 list
前缀 tp 是 test paper 的缩写
list 的每一项是一个 json,json 的字段:
text: Markdown 版的考题文字(使用 Markdown 除了方便排版,还方便插入图片)
options: 选择题的选项
type: 考题类型,包括判断题、选择题、问答题
3.1.2. 读写分析
使用 list,因为考题是有顺序的
写入或更新时,清空 list 然后整体写入,不存在局部修改
读取时,全量读取整个 list
3.2. 在校学生和离校学生考卷文字部分
3.2.1. 数据结构设计
key: 学校 ID + 考试 ID
column family: test
column: paper (JSON 格式)
3.2.2. 读写分析
paper 列直接保存整个考题清单,每张考卷都是全量读取,不存在单独读取某个考题的情形
写入时,是从 Redis 转存过来的,直接按照 key 保存(原 Redis list 直接转为 JSON 数组)
读取时,直接按照 key 读取
3.3. 所有考卷图片部分
3.3.1. 数据结构设计
key: 学校 ID + 考试 ID + 图片 ID
column family: test
column: pic
3.3.2. 读写分析
需要批量步骤缓存时,直接按照前缀读取一个考卷的所有图片
单张读取时,直接按照 key 读取
老师上传考卷时,应用给考卷的所有图片设定顺序自增的图片 ID
4. Redis Sentinel 集群服务器数量和性能
Redis 服务器单机 TPS 5~10 万
请求试卷 5 万/每秒
每次请求试卷只需要读取 Redis 服务器一次就能获取整张试卷(图片不依赖 Redis),用最基础的 3 机 Sentinel 集群就足够了。
评论