设计千万级学生管理系统的考试试卷存储方案
作业要求
基于模块 4 第 6 课的估算结果和 Redis sentinel 的初步方案设计,完善考试试卷存储方案,具体包括:1)完善 Redis 的数据结构设计,明确具体使用哪种 Redis 数据结构
2)设计具体的读写流程(可以文字描述也可以序列图描述,序列图要有文字辅助说明)
3)对照模块 4 第 6 课的性能估算结果,计算 Redis sentinel 集群的服务器数量和性能
性能估算
用户量预估
在这里用户量预估为 1000 万
用户行为建模
用户有以下的关键行为
登录注册
文件上传下载
学生选课
学生考试
性能需求计算
登陆行为
该系统主要管理学生的信息,作业,还有考试等,其中交作业是高频场景,每天每个学生都需要交作业。在这里假设每个学生每天提交 4 次作业,那么登陆的次数就是 1000w * 4 = 4000w。交作业时间是集中在晚 8 点到 10 点,那么登陆的 TPS 要求为: 4000w/(4*3600) = 3000/s
每次登陆会产生一条登陆记录,因此日存储登陆记录为 4000w,假设登陆记录保持 3 个月,那么总的数据量为 4000w x 3 x 30 = 36 亿。每条记录包含学生 ID,登陆时间,登陆 IP,总共为 12 字节,那么数据总量为 36 亿 * 12 = 43G
注册行为
总共 1000w 学生,但是每年只有新生需要注册,假设每年的新生为 250w,而注册时间为 1 个月,那么每天的注册请求为 250w/30 = 8.3w,第一天的注册人数会多一点,所以可以调整到 10w 一天,TPS 为 10w/(12*3600) = 3/s
学生注册后需要存储学生信息,其中
在校学生数据存量=1000w*200 字节=2G,图片数据 1000w*1M = 10T
离校学生数据存量=2G/4=0.5G, 图片数据 10T/4=2.5T
考试行为
每门学科每年 2 次考试,每个学生平均一学期 20 门课,每门考试 20 道判断题,20 道选择题,4 道大题,考试结果永久保存,存储量为
在校学生: 1000w*20(课)*2(考试次数)*1000(答案)*2(学期)*3(年)=2.4T
离校学生: 2.4T/4 = 0.6T
考试提交,假设考试在 1 个月内完成,时间为上午下午各 4 小时,请求试卷在开考前 1 分钟,提交答案在考试结束前 30 分钟
请求试卷: 1000w*20(课)/20(周末不考)/4(每天 4 堂考试)/60 秒=5w/s
提交试卷: 1000w*20(课)/20(周末不考)/4(每天 4 堂考试)/30 分钟=1700/s
存储性能需求
登陆
登录次数: 3000/s
登陆记录: 存储数据量 36 亿条,存储容量 43G,写入 TPS = 登陆 TPS = 3000/s
注册
注册 TPS = 3/s
在校学生数据存储: 基本数据=2G,图片数据=10T
离校学生数据存储: 基本数据每年增长 500M,图片数据增长 2.5T
考试
在校学生考试结果存储: 2.4T
离校学生考试结果存储: 年增长 0.6T
试卷请求 QPS: 5w/s
提交试卷 TPS: 1700/s
存储架构设计
登陆记录
单机->不需要分区->分片架构->数据库分库分表
在校学生基本信息
单机存储->单机写->单机读->自动切换->数据库主备
在校学生图片数据
集群存储->分片架构->HBase
离校学生图片数据
集群存储->分片架构->HBase
考试数据
对于考试结果,有不同的应用场景,分为老师和学生两种应用场景,而每一种应用场景,对于数据的操作都不一样,因此采取两种不同的存储方式,有一定的数据冗余
老师课程考试结果
MySQL 分库分表,因为老师需要对每一个学生的考试结果进行分析,产生报表等等,采用 MySQL 的关系型数据库便于老师的分析操作
学生考试结构
HBase,学生只需要针对自己的考试结果进行查询,不需要对(也不允许)对别人的考试结果进行查询,并且学生也不需要对自己的考试结果进行分析对比,所以采用 HBase 做存储就足够了。
试卷存储和访问
由于此次作业是专门针对考试试卷的存储方案,因此在这里进行详细叙述。根据上一节对考试系统的性能分析,我们知道以下数据
在校学生考试结果存储: 2.4T
离校学生考试结果存储: 年增长 0.6T
试卷请求 QPS: 5w/s
提交试卷 TPS: 1700/s
在最高峰的时候,TPS 能够达到 5w/s,这个是高性能的读请求,因为试卷并不是实时生成,而是由老师提前生成上传,然后考试的时候直接读取。所以是 5w/s 的读请求峰值。并且试卷一旦生成,修改的频率是比较低的,因为 Redis 自带的存储功能可以满足这个需求。并且利用 Redis 的缓存,可以极大的提升考试试卷的读请求性能。
Redis 分析
Redis 是一个开源(BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA 脚本(Lua scripting), LRU 驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis 哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
Redis 的应用场景
速度快,完全基于内存,使用 C 语言实现,网络层使用 epoll 解决高并发问题,单线程模型避免了不必要的上下文切换及竞争条件;缓存,毫无疑问这是 Redis 当今最为人熟知的使用场景,在提升服务器性能方面非常有效。以下是几个使用 Redis 的场景用例:
排行榜,如果使用传统的关系型数据库来做,非常麻烦,而利用 Redis 的 SortSet 数据结构能够非常方便搞定;
计算器/限速器,利用 Redis 中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用 MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个 API 的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力;
好友关系,利用集合的一些命令,比如求交集、并集、差集等,可以方便搞定一些共同好友、共同爱好之类的功能;
简单消息队列,除了 Redis 自身的发布/订阅模式,我们也可以利用 List 来实现一个队列机制,比如到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的 DB 压力,完全可以用 List 来完成异步解耦;
Session 共享,以 PHP 为例,默认 Session 是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用 Redis 保存 Session 后,无论用户落在那台机器上都能够获取到对应的 Session 信息。
Redis sentinel
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
总结
基于以上的分析,所以考试试卷的存储和访问采用 Redis 存储系统,而试卷的存储格式可以是 JSON string 结构,也可以是 list 的结构。每一个题目可以考虑称为 list 里面的一个存储对象。
存储方案设计
存储方案就是设计基于存储系统的数据模型。根据存储架构,我们可以采用以下的存储方案
数据结构设计
学生图片
key: 学校 ID + 学号 ID + pic
Column Family: Info
Column: Pic
学生登陆之后,在界面上显示头像,按照 Key 读取信息
管理员可以查看班级所有同学头像,并且可以按照学号前缀里面的班级信息 scan,比如 20200810 代表 2020 级 8 系 10 班
学生登陆
key: 学校 ID + 学号 ID + timestamp
Column Family: Login
Column: IP
学生查看自己的登陆记录,直接按照前缀聊查询即可
考试结果
key: 学校 ID + 学号 ID + 考试 ID
Column Family: test
Column: result (JSON), score,
学生提交考试结果,直接按照 key 保存 result
老师批改试卷后,直接写入 score
学生查看自己成绩,按照 key 读取 result 和 score,可以看到得分和错误
考试试卷
key:学校 ID + 考试 ID
Column Family: test
Column: questions (list 结构),每一道题目是 list 里面的一个对象
老师准备好试卷之后,直接按照 key 保存试卷
学生考试开始前,按照 key 读取试卷
版权声明: 本文为 InfoQ 作者【9527】的原创文章。
原文链接:【http://xie.infoq.cn/article/8144793bfcdd4686d53f62eab】。文章转载请联系作者。
评论