腾讯开心鼠英语存储演进漫谈

今年腾讯开心鼠项目的用户量每天都在肉眼可见的急剧增长,某些周的复合增长率甚至达到了10%,随着用户量的增长和业务复杂性的增加,数据库的高峰性能压力和存储压力不断变大,下面整体介绍下我们进行的一系列存储架构的调整以及未来的规划。

年初项目的整体架构如下,大部分模块都在以(ip+port)的方式使用同个DB实例。

在量较小的情况下,核心DB的CPU和存储负载都没有太大压力,但量变大后整体风险逐步暴露,主要有:

耦合度高:任何一个业务svr的SQL性能都会影响所有业务的性能,一旦某个svr没控制好,可能整体全挂

扩展性差:整个存储的磁盘空间,都写性能都会受到单机限制。

阶段1:一主多从

结合高峰期数据库审计日志和对业务模块的梳理,发现数据库访问有以下特点:

读写QPS比例大致在10 : 1;

写主要集中在用户的物品数据,课程数据,学习数据等;

读主要分布在账号体系,集训营,管理端,支付物流,学习记录等;

慢查询主要集中在集训营和管理端的业务上。

针对读写比例的特点,优先优化读请求。

在一主一从的基础上扩充两个RO组,从数据实时性和业务重要性对读操作进行拆分。

数据实时性

针对实时性要求高的用户账号,物品数据读取主库;

针对用户课程数据,学习数据读取从库;

业务重要性

APP主功能,支付等读取主库和从库1;集训营和管理端读取从库2;数据统计、分析读取从库3;

进行读库的拆分后,线上运行稳定,主库CPU负载从40%下载到了20%。

阶段2:横向拆分

在整个读拆分过程发现,从业务划分来看,数据库数据主要分为APP用户数据,集训营数据,支付物流数据,运营活动数据;

其中除了用户账号数据是多个业务模块所需的,其他的基本上是独立的,于是我们考虑将数据库进行拆分,拆分为4个数据库:

公共数据库:用户APP账号数据,微信序账号数据,课程静态数据;

集训营数据库:集训营分配数据,上课数据,老师数据;

支付/物流数据库:支付数据,物流数据,运营活动数据;

UGC数据库:用户课程数据,学习数据,活动数据。

每个数据库对应一个modle模块,将各表的增删改查等操作封装,各自以RPC方式去调用其他库的数据库操作。

横向拆分主要涉及代码的改造和数据的迁移,两个核心问题是:

尽可能降低迁移的改造成本

如何保障业务不中断平滑迁移

以UGC数据库为例,该库数据主要分为两大类:

用于数据分析的少量关系化查询的 用户活动数据 ;

存在大量关系化查询的 用户课程数据 。

在用户规模不大时,原有的UGC数据都存储在mysql中,但随着用户数增长,mysql的存储空间和写性能都无法满足诉求。

用户课程数据

该部分数据需要支持关系化查询,我们采用了腾讯云的mysql集群解决方案TDSQL,TDSQL主要优势在于:

整体容量随着分片数的增加而增加,并且是动态扩容的,不影响业务;

读写分离,拥有更好的读写性能;

提供Proxy代理,业务像使用单机Mysql一样;

SQL语句完全兼容,业务迁移成本低;

完备的监控指标和告警支持,同时支持性能分析。

数据表的平滑迁移

选择合适的sharedkey在TDSQL库中创建新的表;

使用mysqldump的数据导出服务,导出已有的全量数据,再导入到TDSQL;

通过腾讯云的DTS(数据传输服务),接入在线教育的统一binlog notify服务,将数据增量变更写入到TDSQL;

将项目中数据库配置进行读库和写库的改造;

进行项目中的该表读操作改造,将其改造到TDSQL对应表;

线上业务监控及观察一段时间,如有问题,及时回滚读库配置;

读请求迁移平稳后,对项目中的该表写操作改造,将其改造到TDSQL对应表;

线上业务及监控观察一段时间,如有问题,及时回滚写库配置;

停止DTS服务和binlog notify服务,迁移完成。

当然我们的UGC业务有一定的延时误差是被允许接受的;如果对延时的接受程度较低,可以采用Mysql,TDSQL的双写方案来进行改造;先迁移写再迁移读来进行迁移改造,这里不再赘述,欢迎随时讨论。

用户活动数据

以用户学习数据为例,主要为用户完成学习活动过程中,在各个环节的表现,该部分数据主要用于数据分析,基本全为写操作,我们最终选择mongdb来存储这部分数据,主要优势在于:

mongdb面向集合存储,模式自由,可以方便地扩展学习数据字段;

mongdb支持大数据量的存储,可以满足这种随时间膨胀的特厉害的存储诉求;

mongdb支持一定程度的关系化查询,满足按用户ID,活动ID来查询数据;

强大的聚合工具,完美配合MapReduce等数据分析工具;

支持数据复制和恢复能力,便于分析数据的传输。

数据表的平滑迁移

在mongdb中建立对应的数据库表

改造项目代码,将原有数据库表的写请求存储到Kafka队列中

使用迁移服务,对原有数据表的全量数据进行迁移,写入到mongdb中

步骤3完成后,启动Kafka对应topic的消费服务,开始将数据平滑写入到mongdb

针对各库的每张表都可以采用类似的方案进行迁移,至此我们可以开始对单库进行优化。

阶段3:单库优化

以公共数据库为例,通过分析业务高峰期的的数据库审计日志,发现:

50%的请求基本集中在用户账号表上,10%的请求集中在课程静态数据上;

下面将讨论如何优化这两部分请求。

用户账号表

该表主要特点有:

写请求占比大致为11:1;

写请求主要集中在密码、login时间等少数字段上;

大部分用户数据:像手机号,ID等基本不变,同时这些字段可接受一定的时延。

于是将用户账号表拆解成两部分:

对于时延不敏感数据拆成一张mysql表,同时将表数据缓存到redis中;

写请求较多的拆解成为redis中的缓存。

对比了业界主要的缓存更新方案,考虑对业务无入侵、实时性优、监控完善等特点;

选用便于接入的在线教育统一的DTS更新服务。

课程相关数据表

该表数据特点:

基本为静态数据表,不常变更;

占用空间大致在10M左右,且可预见的数据都不会很大。

可采用内存缓存的来进行读加速,在服务启动时初始化后定时更新。

同时还对各库进行了一系列的慢查询优化,索引优化,尽量保证单库的可用性及性能。

阶段4:整体优化

在针对性对单库进行性能优化后,在实际开发和维护过程中还存在些痛点:

业务方调用成本高,得理解不同数据库的差异;

多数据库的安全防护,统计功能、监控等功能都很分散;

UGC数据大表业务高峰写入QPS很高;

UGC数据大表无法在线DDL操作。

问题1/2:DataProxy统一代理

除了针对各个svr合理调整连接数外,引入DB代理也是个较好的解决方案;于是采用Data Proxy的方案,由其代理各个数据库的访问,提供统一接口给业务方调用;同时在proxy可进行各种安全防护措施,比如过载保护,缓存崩塌保护,穿透保护等;另外还能对外提供数据统计、延时监控功能。

问题3:Kafka平滑写入

由于业务特点,高峰期用户数在平时的10倍以上,单纯的扩容数据库机器在大部分时间内都是极其浪费的;同时由于数据特点,少量数据的读延迟是可以接受的;于是引入KAFKA队列,先由PROXY统一写入到队列中,再由消费服务平滑写入TDSQL与MongoDB;这样就能较低成本扛住高峰期流量和突增流量,并且有很好的扩展性。

问题4:在线DDL暂停消费服务

对于大表的DDL一直是数据库优化过程中的一个老大难问题;数据库表的变更,不管是索引变更还是字段变更基本都会缩表从而引发业务中断;在引入队列平滑写入情况下,可在线随时停止消费服务后进行的DDL变更,但不会影响正常业务的运转。

最终存储架构

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章