今年腾讯开心鼠项目的用户量每天都在肉眼可见的急剧增长,某些周的复合增长率甚至达到了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变更,但不会影响正常业务的运转。
最终存储架构
网友评论