本文主要讲述vivo评测中泰在数据库设计上的技术探索与实践。一、业务背景随着公司业务的发展和用户规模的增加,很多项目都在打造自己的评论功能,评论的业务形式基本相似。当时每个项目都是独立设计和实施的,重复性工作量很大;并且不同业务之间存在数据孤岛,很难建立连接。因此,我们决定搭建公司级审核业务平台,为各业务方提供审核业务的快速接入。通过分析各大主流APP点评业务的竞品,我们发现大部分点评业务表单都具备评论、回复、二次回复、点赞等功能。具体如下图所示:涉及的核心业务概念有:【话题】评论的话题,商城中的商品,应用商店中的应用,社区中的帖子【评论】用户在话题[回复]用户针对某条评论发表的内容,包括一级回复和二级回复2.数据库存储选择团队在数据库选择和设计时对比了多种主流数据库,最终做出选择MySQL和MongoDB存储。由于评论业务的特殊性,需要具备以下能力:【字段扩展】业务方不同评论模型存储的字段不同,需要支持动态自动扩展。【海量数据】作为公司的中台服务,数据量随着业务方的增加成倍增长,需要具备快速便捷的水平扩展和迁移能力。【高可用】作为中端产品,需要提供快速稳定的读写能力,具备读写分离和自动恢复能力。评论业务不涉及用户资产,对交易的要求不高。因此,我们选择了MongoDB集群作为底层的数据存储方式。3、深入理解MongoDB1。集群架构由于单机在磁盘/IO/CPU等各方面存在瓶颈,所以采用MongoDB提供基于集群的部署架构,如图:主要由以下三部分组成:mongos:路由server负责管理应用端的具体链接。应用端请求mongos服务后,mongos将具体的读写请求转发给对应的shard节点执行。一个集群可以有1~N个mongos节点。config:配置服务器,用于存储分片集合的元数据和配置信息,必须部署为副本集(副本集概念点我)。mongos通过config配置服务端的元数据信息。shard:用于存放集合的分片数据的mongod服务,也必须部署为副本集。2、ShardkeyMongoDB数据存储在collection中(对应MySQL表)。在cluster模式下,collection根据shardkey拆分成多个区间,每个区间形成一个chunk,按照规则分布在不同的shard之间。并形成元数据注册到配置服务进行管理。shardkey只能在创建shardcollection时指定,指定后不能修改。shardingkey主要有两种类型:Hashsharding:通过哈希算法进行散列,数据分布更均匀分散。支持单列和多列哈希。Rangesharding:根据指定shardkey的值分布,连续的key往往分布在连续的区间内,更适合范围查询场景。单数据哈希能力由分片键本身保证。三、中台实践点评1)集群的扩容作为中台服务。针对不同的接入业务方,通过表隔离来区分数据。以评论评论表为例,每个访问的业务方创建一个单独的表。业务方A表为comment_clientA,业务方B表为comment_clientB。访问时都会创建表和对应的索引信息。但是这种设计存在几个问题:单个集群无法满足部分业务数据物理隔离的需求。集群调优(如拆分迁移时间)难以设置不同的业务特性。横向扩展造成的单一业务方数据过于分散。因此,我们对MongoDB的集群架构进行了扩展:扩展注释MongoDB集群增加了【逻辑集群】和【物理集群】的概念。一个业务方属于一个逻辑集群,一个物理集群包含多个逻辑集群。增加路由层设计,应用负责扩展Spring的MongoTemplate和连接池管理,实现业务与MongoDB集群之间的切换和选择服务。不同的MongoDB分片集群实现了物理隔离和差异化调优的可能性。2)shardkey的选择在MongoDB集群中,一个collection的数据部署是分散在多个shard和chunk中的,我们希望一个评论列表的查询应该只访问一个shard,所以范围确定分片的方法.最初,只有一个键用作分片键。以评论表为例。主要字段是{"_id":uniqueid,"topicId":topicid,"text":textcontent,"createDate":time},考虑对一个topicid的评论尽可能连续分发,分片我们设置的键是topicId。通过性能测试的介入,我们发现了两个非常致命的问题:jumbochunk问题uniquekey问题jumbochunk:根据官方文档,MongoDB中的chunksize限制在1M-1024M。shardkey的值是chunk划分的唯一依据。当连续写入的数据量超过chunksize设置值时,MongoDB集群会自动分裂或迁移。但是写入同一个shardkey属于一个chunk,不能拆分,会造成jumbochunk问题。比如我们设置一个chunk的大小为1024M,单个文档为5KB,那么单个chunk大约可以存储21W个文档。考虑到热点评论(比如微信评论),评论数可能达到40W+,那么单个chunk很容易超过1024M。超过最大大小的chunk仍然可以提供读写服务,但是不会被拆分和迁移,长此以往会造成集群间的数据不平衡。Uniquekey问题:MongoDB集群的uniquekey设置增加了限制,必须包含shardkey;如果_id不是shardkey,_id索引只能保证在单个shard上的唯一性。不能在哈希索引上指定唯一约束对于待分片的集合,如果集合有其他唯一索引,则不能对该集合进行分片对于已经分片的集合,不能在其他字段上创建唯一索引所以我们删除了数据和集合,调整topicId和_id为联合分片键重新创建集合。这不仅打破了块大小的限制,也解决了唯一性问题。4.随着数据的写入,当单个chunk中的数据大小超过指定大小(或chunk中的文件数超过指定值)时,会进行迁移扩容。MongoDB集群在插入或更新时会自动触发chunk分裂。拆分会导致集合中块的分布不均匀,在这种情况下,MongoDB平衡器组件会触发块在集群之间的迁移。平衡器组件是管理数据迁移的后台进程。如果分片之间的块数差异超过阈值,平衡器将执行自动数据迁移。balancer可以在线迁移数据,但是迁移过程会对集群的负载产生很大的影响。一般建议MongoDB的扩容在低业务时间(详见官网)通过以下设置进行。只需要准备一个新的shard复制集并在mongos节点上执行即可:在扩容期间,由于chunk的迁移,也会降低集群的可用性,所以只能在业务低峰的时候进行.1亿+条评论回复数据存储相对稳定。BSON非结构化数据也支持我们多版本业务的快速升级。流行的数据内存存储引擎,大大提高了数据读取的效率。但是对于MongoDB来说,集群部署是一个不可逆的过程。集群之后,也带来了很多限制,比如索引和分片策略。所以一般业务使用MongoDB时,副本集的方式可以支持TB级的存储和查询,不一定需要集群的方式。以上内容基于MongoDB4.0.9版本的特性,与最新版本的MongoDB细节略有不同。
