1、简介
这是一种将海量的数据水平扩展的数据库集群系统,数据分表存储在sharding的各个节点上,使用者通过简单的配置就可以很方便地构建一个分布式的MongoDB集群。 那么首先我们应该理解何为分片(sharding)以及它的基本工作模式。
2、什么是分片
分片(sharding)是MongoDB用来将大型集合分割到不同服务器(或者说一个集群)上所采用的方法,尽管分片起源于关系型数据库分区,但它(像MongoDB大部分方面一样)完全是另外一回事。
和你可能使用过的任何分区方案相比,MongoDB的最大区别在于它几乎能自动完成所有事情,只要告诉MongoDB要分配数据,它就能自动维护数据在不同服务器之间的均衡,当然你得告诉MongoDB把服务器添加集群中,不过只要这么做了,MongoDB同堂会确保新加入的服务器分得均等的数据。
分片主要是为了实现3个简单的目标。
- 让集群"不可见"
应用程序只要知道跟它打交道的是一个普通的mongod实例就够了,为了实现这一目标,MongoDB自带了一个叫做mongos的专有路由进程。mongos坐镇集群大前方,对连上它的任何应用而言就像是一个普通的mongod服务器,mongos会把请求正确无误地转发到集群中一个或者一组服务器上,接着再把获得响应拼装起来发回给客户端,这样一来,客户端无需知道与其通信的是一台服务器还是一个集群。
不过由于集群本身的特性使然,也存在一些违背该抽象的特殊情况,这些特殊情况会在后续的教程中提到。
- 保证集群总是可读写
任何集群都无法保证永远可用(比如出现大范围停电之类的情况),但是在合理的条件下,永远都不应该出现用户无法读写数据的情况,在功能发生明显降级前,集群应当允许尽可能多的节点失效。
MongoDB通过多种途径来确保最长的正常运行时间,集群的每一部分而已并且应当在其他服务器上(最理想的情况是在其他数据中心)有冗余的进程运行,以便当一个进程、机器、数据中心坏掉了,其他副本可以立即(自动地)接替坏掉的部分继续工作。
把数据从一台服务器迁移到另外一台服务的过程中也存在着一个非常有趣的难题:在传输过程中如何保证对数据访问的持续性和一致性?我们已经找出了一些非常好的而解决方案,不过有些超出本身的讲述范围,宗旨,MongoDB采用了一些非常漂亮的技巧。
- 使集群易于扩展
当系统需要更多的空间或者资源时,应当可以添加,MongoDB支持按需扩充系统容量,要实现这些目标,一个集群应该易于使用和易于管理(否则添加新的分片就不那么容易了),MongoDB能够轻易地让应用程序自然邹壮地成长。
3、理解分片
为了建立、管理或者调试集群,需要了解分片的基本工作模式,接下来我们从如下几个部分了解分片的基本工作模式。
3.1、分割数据
分片(sharding)是集群中负责数据某一子集的一台或者多台服务器,举个例子,如果有一个集群存储了1000000份代表网站用户的文档,则一个分片可能包含其中200000位用户的信息。
一个分片可由多台服务器组成,如果分片包含不止一台服务器,则每台服务器都有一份完全相同的数据字节的副本,在生产环境中,一个分片通常是一个副本集(replica set)。
上图中一个分片包含数据的某一个子集,若某一个分片包含多台服务器,则每台服务器拥有一份完整的数据副本。
为了在分片间均匀地分配数据,MongoDB会在不同的分片间移动数据子集,它会根据片键来决定移动哪些数据,比如我们可能选择按用户名(username)字段来划分用户集合,MongoDB使用基于区间的方法进行划分,即按照给定区间将数据分割成不同块,比如["a","f"),注意,在该章节中使用标准的区间符号来描述区间。"["和"]"表示闭区间,而"("和")“表示开区间,因此,有4种可能的区间:
x在(a,b)中,表示当且仅当存在x使得a<x<b。
x在(a,b]中,当且仅当存在x使得a<x<=b。
x在[a,b)中,当且仅当存在x使得a<=x<b。
x在[a,b]中,当且仅当存在x使得a<=x<=b。
MongoDB中分片多用[a,b)来表示区间范围,所以这会是最常见的形式,该区间可以表述为:从a开始且包含a,到b为止但是不包含b。
3.2、分配数据
MongoDB划分数据的方法有些不直观,为了理解这么做的理由,我们先使用初级方法,遇到问题时再寻找更好的方法。
- 一分片一区间
分配数据到分片最简单的方法是让每个分片负责一个区间的数据,所以,如果我们有4个分片,则很可能会得到如下图所示的设置,在这个实例中,我们假设所有的用户名都以a到z之间的字母开头,其范围可以表示区间["a","{"),其中{是ASCII码中字母z后面得字符。
在上面图中这4个分片,区间分别是["a","f")、["f"、"n")、["n"、"t")和["t","{")
这种分片体系非常简单易懂,但是在一个大型或者繁忙的系统中却会带来许多不便,如此分片导致的后果是什么呢?
假设许多用户首字母的范围["a","f")中的名字来注册,这会导致分片1较大,因此我们需要拿出它的一部分文档将其挪到分片2上去,我们可以调整区间使分片1(比如说)变成["a","c"),使分片2变成["c","n"),如下图所示:
上图中分片1迁移部分数据到分片2,分片1的区间缩小而分片2的区间扩大。
上图中,一分片一区间会导致级联效应:必须易懂数据到下一台服务器上,即时不能改善平衡性。
如果添加一个新分片又会怎么样呢?假设这个集群继续工作下去,最终每个分片上都有了500G数据,然后我们添加一个新分片进来,现在我们不得不把400G数据从分片4挪到分片5上,将300G从分片3挪到分片4上,将200G从分片2挪到分片3上,将100G从分片1挪到分片2,整整移动了1TB的数据!如下图所示:
上图中描述,添加一台服务器并使集群负载均衡,我们可以通过在集群的中间(分片2和分片3之间)添加服务器来降低所需迁移的数据量,但即时这样仍然需要迁移600G的数据。
随着分片数量和数据量的增长,这种级联效应只会持续恶化下去,因此MongoDB并不采用这种方式来分配数据,而是使每个分片包含多个区间。
- 一分片多区间
让我们重新回顾上述的一片区一分区的情况,其中分片1和分片2各有500G的数据,而分片3个分片4各有300G数据,这次我们允许每个分片包含多块区间。
我们可以把分片1上的数据划分为两个区间,使其中一个包含400G数据(比如说["a","d"),另外一个包含100G数据["d","f"),接着我们对分片2也做同样的处理,得到区间["f","j")和["j","n"),现在我们可以把100G数据["d","f")从分片1迁移都分片4上,把区间["j","n")内的所有文档从分片2迁移到分片3上,如下图所示:
说明:
一个区间的一个数据称为一个数据块(也叫块,chunk),当我们把一个块得区间一分为二时,一个块也就变成了两个块。
如果添加新分片,MongoDB可以从每个分片顶端取100G数据并把这些块挪到新分片上,使得新分片获取400G数据需要移动的数据量最小化,只有400G。如下图所示:
说明:
上述图中添加分片时,每个分片都可以直接给它提供数据。
这就是MongoDB的数据分配之道,当一个块变得越来越大时,MongoDB会自动将其分割成两个较小的块,如果分片间比例失调,则MongoDB会通过迁移块来确保均衡。