本文主要讲述 vivo 评论中台在数据库设计上的技术探索和实践。java
随着公司业务发展和用户规模的增多,不少项目都在打造本身的评论功能,而评论的业务形态基本相似。当时各项目都是各自设计实现,存在较多重复的工做量;而且不一样业务之间数据存在孤岛,很难产生联系。所以咱们决定打造一款公司级的评论业务中台,为各业务方提供评论业务的快速接入能力。在通过对各大主流 APP 评论业务的竞品分析,咱们发现大部分评论的业务形态都具有评论、回复、二次回复、点赞等功能。算法
具体以下图所示:mongodb
涉及到的核心业务概念有:数据库
【主题 topic】评论的主题,商城的商品、应用商店的 APP、社区的帖子服务器
【评论 comment】用户针对于主题发表的内容微信
团队在数据库选型设计时,对比了多种主流的数据库,最终在 MySQL 和 MongoDB 两种存储之进行抉择。架构
因为评论业务的特殊性,它须要以下能力:数据库设计
【字段扩展】业务方不一样评论模型存储的字段有必定差别,须要支持动态的自动扩展。ide
【海量数据】做为公司中台服务,数据量随着业务方的增多成倍增加,须要具有快速便捷的水平扩展和迁移能力。性能
而评论业务不涉及用户资产,对事务的要求性不高。所以咱们选用了 MongoDB 集群 做为最底层的数据存储方式。
因为单台机器存在磁盘/IO/CPU等各方面的瓶颈,所以以 MongoDB 提供集群方式的部署架构,如图所示:
主要由如下三个部分组成:
mongos:路由服务器,负责管理应用端的具体连接。应用端请求到mongos服务后,mongos把具体的读写请求转发到对应的shard节点上执行。一个集群能够有1~N个mongos节点。
config:配置服务器,用于分存储分片集合的元数据和配置信息,必须为 复制集(关于复制集概念戳我) 方式部署。mongos经过config配置服务器合的元数据信息。
MongoDB 数据是存在collection(对应 MySQL表)中。集群模式下,collection按照 片键(shard key)拆分红多个区间,每一个区间组成一个chunk,按照规则分布在不一样的shard中。并造成元数据注册到config服务中管理。
分片键只能在分片集合建立时指定,指定后不能修改。分片键主要有两大类型:
hash分片:经过hash算法进行散列,数据分布的更加平均和分散。支持单列和多列hash。
做为中台服务,对于不一样的接入业务方,经过表隔离来区分数据。以comment评论表举例,每一个接入业务方都单首创建一张表,业务方A表为 comment_clientA ,业务方B表为 comment_clientB,均在接入时建立表和相应索引信息。但只是这样设计存在几个问题:
单个集群,不能知足部分业务数据物理隔离的须要。
集群调优(如split迁移时间)很难业务特性差别化设置。
所以咱们扩展了 MongoDB的集群架构:
扩展后的评论MongoDB集群 增长了 【逻辑集群】和【物理集群】的概念。一个业务方属于一个逻辑集群,一个物理集群包含多个逻辑集群。
增长了路由层设计,由应用负责扩展Spring的MongoTemplate和链接池管理,实现了业务到MongoDB集群之间的切换选择服务。
MongoDB集群中,一个集合的数据部署是分散在多个shard分片和chunk中的,而咱们但愿一个评论列表的查询最好只访问到一个shard分片,所以肯定了 范围分片 的方式。
起初设置只使用单个key做为分片键,以comment评论表举例,主要字段有{"_id":惟一id,"topicId":主题id,"text":文本内容,"createDate":时间} ,考虑到一个主题id的评论尽量连续分布,咱们设置的分片键为 topicId。随着性能测试的介入,咱们发现了有两个很是致命的问题:
jumbo chunk问题
jumbo chunk:
官方文档中,MongoDB中的chunk大小被限制在了1M-1024M。分片键的值是chunk划分的惟一依据,在数据量持续写入超过chunk size设定值时,MongoDB 集群就会自动的进行分裂或迁移。而对于同一个片键的写入是属于一个chunk,没法被分裂,就会形成 jumbo chunk 问题。
举例,若咱们设置1024M为一个chunk的大小,单个document 5KB计算,那么单个chunk可以存储21W左右document。考虑热点的主题评论(如微信评论),评论数可能达到40W+,所以单个chunk很容易超过1024M。超过最大size的chunk依然可以提供读写服务,只是不会再进行分裂和迁移,长久以往会形成集群之间数据的不平衡.
惟一键问题:
MongoDB 集群的惟一键设置增长了限制,必须是包含分片键的;若是_id不是分片键,_id索引只能保证单个shard上的惟一性。
You cannot specify a unique constraint on a hashed index
For a to-be-sharded collection, you cannot shard the collection if the collection has other unique indexes
所以咱们删除了数据和集合,调整 topicId 和 _id 为联合分片键 从新建立了集合。这样即打破了chunk size的限制,也解决了惟一性问题。
随着数据的写入,当单个chunk中数据大小超过指定大小时(或chunk中的文件数量超过指定值)。MongoDB集群会在插入或更新时,自动触发chunk的拆分。
拆分会致使集合中的数据块分布不均匀,在这种状况下,MongoDB balancer组件会触发集群之间的数据块迁移。balancer组件是一个管理数据迁移的后台进程,若是各个shard分片之间的chunk数差别超过阈值,balancer会进行自动的数据迁移。
balancer是能够在线对数据迁移的,可是迁移的过程当中对于集群的负载会有较大影响。通常建议能够经过以下设置,在业务低峰时进行(更多见官网)
db.settings.update( { _id: "balancer" }, { $set: { activeWindow : { start : "<start-time>", stop : "<stop-time>" } } }, { upsert: true } )
MongoDB的扩容也很是简单,只须要准备好新的shard复制集后,在 Mongos节点中执行:
sh.addShard("<replica_set>/<hostname><:port>")
扩容期间由于chunk的迁移,一样会致使集群可用性下降,所以只能在业务低峰进行
MongoDB集群在评论中台项目中已上线运行了一年多,过程当中完成了约10个业务方接入,承载了1亿+评论回复数据的存储,表现较为稳定。BSON非结构化的数据,也支撑了咱们多个版本业务的快速升级。而热门数据内存化存储引擎,较大的提升了数据读取的效率。
但对于MongoDB来讲,集群化部署是一个不可逆的过程,集群化后也带来了索引,分片策略等较多的限制。所以通常业务在使用MongoDB时,副本集方式就能支撑TB级别的存储和查询,并不是必定须要使用集群化方式。
以上内容基于MongoDB 4.0.9版本特性,和最新版本的MongoDB细节上略有差别。
参考资料:https://docs.mongodb.com/manual/introduction/
做者:vivo 官网商城开发团队