MongoDB 进阶模式设计

 

从好久之前,我就开始接触开源产品:从最开始的使用、受益者到后来的贡献者,到如今的热情推广者。如今,我是MongoDB的技术顾问。个人职责是为MongoDB的客户和用户提供MongoDB使用的一些最佳实践,包括模式设计、性能优化和集群部署方案等方面。javascript

MongoDB 模式设计进阶案例_页面_01

今天的话题是进阶模式,因此我假设在坐各位至少是已经对MongoDB有了一些基本的了解。 不过每次总有一些同窗觉得这里有水果吃才坐进来的,因此在这里我简单介绍一下:MongoDB 不是芒果(mango),它在拉丁文中的原意是巨大的意思。若是用一句话来归纳的话,mongo是一个高可用、分布式、无模式的文档数据库。等一下,这里我故意用错了一个词: 不是无模式,而是“灵活模式”。 若是真的是无模式,今天我就不用站在这里了。没有模式何来模式设计之说。在你开始用mongo作一些 prototype的时候,确实不用考虑太多的模式。MongoDB内存数据库的一些特性,让你在前期不会遇到什么问题。可是一旦涉及到几千万几十亿的数据量,或者是数千数万的并发量,模式设计就是个你必须提早面对的问题。java

MongoDB 模式设计进阶案例_页面_02

在咱们谈mongo的模式设计以前,咱们颇有必要来了解一下MongoDB的数据模型。你们都知道,不管你从哪一个角度来看,MongoDB都是目前NoSQL,或者说非关系型的数据库中的领头羊。那么,mongo和传统关系数据库的最本质的区别在那里呢?咱们说是它的文档模型。mongodb

MongoDB 模式设计进阶案例_页面_03

关系模型和文档模型的区别在哪里?数据库

  • 关系模型须要你把一个数据对象,拆分红零部件,而后存到各个相应的表里,须要的是最后把它拼起来。举例子来讲,假设咱们要作一个CRM应用,那么要管理客户的基本信息,包括客户名字、地址、电话等。因为每一个客户可能有多个电话,那么按照第三范式,咱们会把电话号码用单独的一个表来存储,并在显示客户信息的时候经过关联把须要的信息取回来。
  • 而MongoDB的文档模式,与这个模式大不相同。因为咱们的存储单位是一个文档,能够支持数组和嵌套文档,因此不少时候你直接用一个这样的文档就能够涵盖这个客户相关的全部我的信息。关系型数据库的关联功能不必定就是它的优点,而是它可以工做的必要条件。 而在MongoDB里面,利用富文档的性质,不少时候,关联是个伪需求,能够经过合理建模来避免作关联。

虽然MongoDB的模型和关系型大相径庭,可是关系型数据库的一些必不可少的功能如动态查询、二级索引、聚合等在MongoDB中也有很是完善的支持。设计模式

MongoDB 模式设计进阶案例_页面_04

这里我介绍一下文档模型的优势:数组

  • 读写效率高-因为文档模型把相关数据集中在一块,在普通机械盘上读数据的时候不用花太多时间去定位磁头,所以在IO性能上有先天独厚的优点;
  • 可扩展能力强-关系型数据库很难作分布式的缘由就是多节点海量数据关联有巨大的性能问题。若是不考虑关联,数据分区分库,水平扩展就比较简单;
  • 动态模式-文档模型支持可变的数据模式,不要求每一个文档都具备彻底相同的结构。对不少异构数据场景支持很是好;
  • 模型天然-文档模型最接近于咱们熟悉的对象模型。从内存到存储,无需通过ORM的双向转换,性能上和理解上都很天然易懂。

MongoDB 模式设计进阶案例_页面_05

那么咱们如何考虑MongoDB 文档模式设计的基本策略呢?性能优化

  • 其实很简单,咱们通常建议的是先考虑内嵌, 直接按照你的对象模型来设计你的数据模型。若是你的对象模型数量很少,关系不是很复杂,那么恭喜你,可能直接一种对象对应一个集合就能够了。
  • 内嵌是文档模型的特点,能够充分利用MongoDB的富文档功能来享受咱们刚才谈到的一些文档模型的性能和扩展性等特性。通常的一对1、一对多关系,好比说一我的多个地址多个电话等等均可以放在一个文档里用内嵌来完成。
  • 可是有一些时候,使用引用则难以免。好比说, 一个明星的博客可能有几十万或者几百万的回复,这个时候若是把comments放到一个数组里,可能会超出16M的限制。这个时候你能够考虑使用引用的方式,在主表里存储一个id值,指向另外一个表中的 id 值。使用引用要注意的就是:从性能上讲,通常咱们可能须要两次以上才能把须要的数据取回来。更加剧要的是:须要把数据存放到两个集合里,可是目前为止MongoDB并不支持跨表的事务性,因此对于强事务的应用场景要谨慎使用。

MongoDB 模式设计进阶案例_页面_06

不少时候咱们并不能很好地回答本身的问题,包括刚才的内嵌仍是引用的问题。那么这个时候有必要了解一下,MongoDB模式设计的终极原则。MongoDB的模式设计和关系型大不相同,咱们说MongoDB是为应用程序设计的,而不是为了存储优化的。若是能够达到最高性能的话,咱们甚至能够作一些反范式的东西。 接下来咱们来看几个比较具体的设计案例,了解一下MongoDB的模式设计思路:服务器

MongoDB 模式设计进阶案例_页面_07

我这里准备了4个比较经典的MongoDB案例,从CMS 内容管理到电商,社交到物联网。 因为时间缘由我就从第二个开始。微信

MongoDB 模式设计进阶案例_页面_08

在电商方面MongoDB的应用场景其实蛮多,好比说,大名鼎鼎的京东用mongo来存储过亿的商品信息,另外有一家著名的境外电商从头至尾用的都是MongoDB,包括订单管理等。这里咱们就来看一下购物车这个场景。购物车的特色就是单个购物车数据项不会太大,通常来讲不会超过100项目。双十一的时候淘宝的购物车里最多就只能放99件商品。在这里我要谢谢个人太太,是她让我知道了这个限制。另一点就是购物车的数据可能须要过时删除。网络

MongoDB 模式设计进阶案例_页面_09

咱们说文档模型在这种场景会是个很好的选择:

MongoDB 模式设计进阶案例_页面_10

你们看一下下面的参考数据模型,第一点注意咱们可使用MongoDB的TTL 索引来自动清理过时数据。TTL索引能够创建在任意一个时间字段上,在创建索引的时候能够指定文档在过多少时间后会被自动清理掉。第二个你们注意的是什么呢?在这里咱们把商品的一些主要信息放到购物车里了,好比说 name,price, quantity,为何? 读一次全部信息都拿到了:价格、数量等等,不须要再去查另外一张表。这是一种比较常见的优化手段,用冗余的方式来提供读取性能。

MongoDB 模式设计进阶案例_页面_11

接下来咱们看一下使用这种模式的时候如何进行一些购物车的操做。好比说,若是咱们想要往购物车里增长一个价值2元的面包,咱们能够用下面的update语句。注意$push的用法。$push 相似于javascript的操做符,意思是往数组尾部增长一个元素。

MongoDB 模式设计进阶案例_页面_12

若是须要更新购物车中某个产品的数量,你能够用update语句直接操做数组的某一个元素。在这里咱们须要作的是更新item 4567的数量为5。 注意 items.$.quanity的使用,这里的$ 表示在查询条件里匹配上的数组元素的序数。

MongoDB 模式设计进阶案例_页面_13

若是须要统计一下在购物车内某个商品的总数,可使用MongoDB的聚合功能。聚合运算在MongoDB里面是对数据输入源进行一系列的运算。在这里咱们作的就是几个步骤是:

  1. $match: 在全部购物车中过滤掉其余商品,只选出id是8910的商品
  2. $unwind: 把items 数组展开,每一个数组元素变成一个文档
  3. $group: 用聚合运算 $sum 把每一件商品的数量相加得到总和

MongoDB 模式设计进阶案例_页面_14

下面咱们来看一个社交网络的例子。社交app最关键的一些场景就是维护朋友关系以及朋友圈或微博墙等。

MongoDB 模式设计进阶案例_页面_15

对于关系描述,使用文档模型的内嵌数组特性,咱们能够很容易地把我关注的用户(following)和关注个人用户表示出来。下例表示TJ个人关注的用户是mandy和bert,而oscar和mandy则在关注我。这种模式是文档模型中最经典的。可是有一个潜在问题就是若是TJ我是一个明星,他们关注个人人可能有千万。一个千万级的数组会有两个问题:1) 有可能超出一个文档最大16M的硬性限制; 2) MongoDB数组太大会严重影响性能。

MongoDB 模式设计进阶案例_页面_16

怎么办?咱们能够创建一个专门的集合来描述关注关系。这里就是一个内嵌和引用的经典选择。咱们但愿用内嵌,可是若是数组维度太大,就须要考虑用另一个集合的方式来表示一对多的关系(用户 1–N 关注者)

MongoDB 模式设计进阶案例_页面_17

另一个要注意的是关注数,咱们在显示关注和粉丝数量的时候,不但愿去跑一次count 查询再显示。由于count操做通常来讲会比较占资源。一般的作法能够再用户对象里面加两个字段,一个是关注数一个是粉丝数。每次有人关注或者关注别人时候就更新一下。

MongoDB 模式设计进阶案例_页面_18

下面咱们来看看比较有趣的微博墙,或者微信朋友圈的实现有什么考量。

MongoDB 模式设计进阶案例_页面_19

在实现微博墙的时候,有两种方式能够考虑:扇出读 或者是扇出写

MongoDB 模式设计进阶案例_页面_20

扇出读、扇出写的说法是基于社交网络的海量用户、海量数据的应用特征。这些大量的数据每每分布在各个分片服务器上。扇出读是一种比较常规的作法,就是当你须要去得到全部你关注用户的最新更新的时候,你就去到每个你关注用户的数据区,把最新的一些数据取回来。由于须要去到不一样的分片服务器去取,因此叫作扇出读。你们能够想象,这种扇出读的效率不会过高,基本上是最慢的那个服务器的响应时间决定了整体的响应时间。 固然,这种方式是比较简单的,不须要特殊处理。

MongoDB 模式设计进阶案例_页面_21

扇出写,我称之为土豪玩法。具体来讲就是当发布的时候,一条数据会写屡次,直接写到每个关注你的粉丝的墙上。这样作的好处是当你的粉丝读他本身的微博墙的时候,他只须要去一个地方就能够把全部最新的更新连续取回来。因为一个用户的数据可通常能够存储在同一台服务器上的同一个区域,经过这种方式能够实现快速的读取微博墙数据。 代价固然也是很明显: 你的写入需求会被放大几十几百倍,存储也是相应的扩大几十几百倍。这个绝对不是关系型数据库的玩法,可是在MongoD 模式设计,这个很正常。只要保证性能,什么事情都作得出来。

MongoDB 模式设计进阶案例_页面_22

MongoDB 模式设计进阶案例_页面_23

下面这个例子,首先是mandy在发消息的时候会写(push)到个人墙上(timeline)来。若是mandy有50个关注者,那么这个写就会有50次,每一个关注者一次。

第二条语句就是我打开微博的时候,一条语句,一个地方就能够找到全部我朋友发的状态更新。注意:这里还使用了bucket,这是另一个控制文档内数组元素个数的有效方法。好比说咱们定义bucket 大小是1000的话,超过1000 就把新的数据插入到下一个文档并对bucket 序数递增。

MongoDB 模式设计进阶案例_页面_24

好了,最后咱们来看一下物联网的应用场景:

MongoDB 模式设计进阶案例_页面_25

各位还有多少人仍然记得MH370,去年在印度洋消失的客机?在该事故以后,许多人都在疑惑:在当今的技术水平下,为何咱们不能跟踪如此庞大的一个东西?

MongoDB 模式设计进阶案例_页面_26

让咱们来看看若是要监控飞机数据有什么样的挑战。飞机上面的数据源众多,光收集位置信息,就须要多个系统协做完成, 如ADS-C, EUROCONTROL等等。此外,收集的数据也是各类各样:位置是2D、速度是数值、引擎参数则是多维度的。

MongoDB 模式设计进阶案例_页面_27

另外一个挑战就是海量数据。一个三小时的航班,每分钟采集一次,少说点,每次100条数据,那就是每秒1万8千个数据点。按天天100,000航班,一天的数据算下来有18亿条,1.8TB 左右的数据, 21,000 的QPS。 从哪一个角度来看,这都是个经典的大数据问题。

MongoDB 模式设计进阶案例_页面_28

这个问题在关系型数据库解决的话,比较幼稚的方法就是设计一个超宽的表。全部须要采集的每个值就是一个列。这种设计的问题比较明显:

  1. 容易形成空白浪费,不是每一条记录都包含全部字段值
  2. 可能会常常须要改数据库模式。对于海量数据,改一次模式代价巨大。

MongoDB 模式设计进阶案例_页面_29

另外一种改良方案是用EAV 设计模式。就是采用一个主表和一个属性值表。在属性值表里存放全部的参数键值对。这样作的好处天然是灵活性:增长新的参数时无需修改模式。可是问题一样存在:用来存储值的那列METRIC_VALUE的字节大小必须定义成全部值的最大值 才能够放下全部的参数值。这个可能带来空间浪费,可是更严重的问题是:将不太可能在此字段上建索引,进而影响一些场景的使用。

MongoDB 模式设计进阶案例_页面_30

下面咱们来看看文档模型怎么作: 这里对于location 、speed 等不一样数据类型的字段,在文档模型下能够直接支持。下面的两个文档,第一个文档和第二个文档能够同属一个集合,可是能够有彻底不一样的字段。 MongoDB对异构数据的支持在这样的场景下有得天独厚的优点。若是咱们但愿对某一个metric如location创建索引,咱们也可使用mongoDB的稀疏索引 (Sparse Index)仅对有location字段的文档建索引,在不形成索引空间浪费的前提下提升检索效率。当须要增长新的字段的时候,也不须要对模式作任何修改,能够直接就在应用中的JSON模型里添加须要的字段(elevation)。

MongoDB 模式设计进阶案例_页面_31

在IOT这个场景里,咱们可使用一个叫作分桶的设计方式来进行几十倍的性能增加。具体来讲就是把采集的数据按小时为一个桶,把每小时的数据聚合到一个文档里。以下面所示,每分钟的值用子文档的一个字段来表示。这样作的好处就是大量减小文档的数量,相应的索引数量也会减小,整体写入IO将会大幅度下降并获得性能提高。

MongoDB 模式设计进阶案例_页面_32

 

使用这种方式咱们还能够把一些统计须要的数值,如每小时的平均值预先就做为一个字段存进去,须要的时候不用现场计算,只要从文档里读出来便可。

MongoDB 模式设计进阶案例_页面_33

MongoDB 模式设计进阶案例_页面_34

小结一下,冗余、扇出写、分桶,这些都是mongodb 的一些经常使用优化手段。 你们能够看到,经过减小额外查询或者关联的需求,经过使用冗余、额外存储的很是规方式,咱们但愿作到的是性能上的最高提高。

MongoDB 模式设计进阶案例_页面_35

MongoDB 中国团队正在扩张中。但愿和一流的、创新的数据库团队一块儿工做吗?加入咱们吧,咱们在寻找有开发架构或者数据库相关经验的大牛们加入咱们的技术顾问阵营。有兴趣?加微信 tjtang826 私聊吧!

相关文章
相关标签/搜索