为了账号安全,请及时绑定邮箱和手机立即绑定

怎么选择MongoDB的分片字段

/ 猿问

怎么选择MongoDB的分片字段

不负相思意 2019-03-20 14:13:27

怎么选择MongoDB的分片字段


查看完整描述

3 回答

?
泛舟湖上清波郎朗

  将存储在MongoDB数据库中的Collection进行分片需要选定分片Key(Shard key),对于分片Key的选定直接决定了集群中数据分布是否均衡、集群性能是否合理。那么我们究竟该选择什么样的字段来作为分片Key呢?有如下几个需要考虑点。
  以下述记录日志的Document为例:
  {

  server : "ny153.example.com" ,
  application : "apache" ,
  time : "2011-01-02T21:21:56.249Z" ,
  level : "ERROR" ,
  msg : "something is broken"
  }
  基数
  Mongodb中一个被分片的Collection的所有数据都存放在众多的Chunk中。一个Chunk存放分片字段的一个区间范围的数据。选择一个好的分片字段非常重要,否则就会遭遇到不能被拆分的大Chunk。
  用上述的日志为例,如果选择{server:1}来作为一个分片Key的话,一个server上的所有数据都是在同一个Chunk中,很容易想到一个Server上的日志数据会超过200MB(默认Chunk大小)。如果分片Key是{server:1,time:1},那么能够将一个Server上的日志信息进行分片,直至毫秒级别,绝对不会存在不可被拆分的Chunk。
  将Chunk的规模维持在一个合理的大小是非常重要的,只有这样数据才能均匀分布,并且移动Chunk的代价也不会过大。
  写操作可扩展
  使用分片的一个主要原因之一是分散写操作。为了实现这个目标,尽可能的将写操作分散到多个Chunk就尤为重要了。
  用上述的日志实例,选择{time:1}来作为分片key将导致所有的写操作都会落在最新的一个Chunk上去,这样就形成了一个热点区域。如果选择{server:1,application:1,time:1}来作为分片Key的话,那么每一个Server上的应用的日志信息将会写在不同的地方,如果有100个Server和应用对,有10台Server,那么每一台Server将会分担1/10的写操作。
  查询隔离
  另外一个需要考虑的是任何一个查询操作将会由多少个分片来来提供服务。最理想的情况是,一个查询操作直接从Mongos进程路由到一个Mongodb上去,并且这个Mongodb拥有该次查询的全部数据。因此,如果你知道最为通用的查询操作的都以server作为一个查询条件的话,以Server作为一个起始的分片Key会使整个集群更加高效。
  任何一个查询都能执行,不管使用什么来作为分片Key,但是,如果Mongos进程不知道是哪一个Mongodb的分片拥有要查询的数据的话,Mongos将会让所有的Mongod分片去执行查询操作,再将结果信息汇总起来返回。显而易见,这回增加服务器的响应时间,会增加网络成本,也会无谓的增加了Load。
  排序
  在需要调用sort()来查询排序后的结果的时候,以分片Key的最左边的字段为依据,Mongos可以按照预先排序的结果来查询最少的分片,并且将结果信息返回给调用者。这样会花最少的时间和资源代价。
  相反,如果在利用sort()来排序的时候,排序所依据的字段不是最左侧(起始)的分片Key,那么Mongos将不得不并行的将查询请求传递给每一个分片,然后将各个分片返回的结果合并之后再返回请求方。这个会增加Mongos的额外的负担。
  可靠性
  选择分片Key的一个非常重要因素是万一某一个分片彻底不可访问了,受到影响的Chunk有多大(即使是用貌似可以信赖的Replica Set)。
  假定,有一个类似于Twiter的系统,Comment记录类似如下形式:
  {

  _id: ObjectId("4d084f78a4c8707815a601d7"),
  user_id : 42 ,
  time : "2011-01-02T21:21:56.249Z" ,
  comment : "I am happily using MongoDB",
  }
  由于这个系统对写操作非常敏感,所以需要将写操作扁平化的分布到所有的Server上去,这个时候就需要用id或者user_id来作为分片Key了。使用Id作为分片Key有最大粒度的扁平化,但是在一个分片宕机的情况下,会影响几乎所有的用户(一些数据丢失了)。如果使用User_id作为分片Key,只有极少比率的用户会收到影响(在存在5个分片的时候,20%的用户受影响),但是这些用户会再也不会看到他们的数据了。
  索引优化
  正如在别的章节中提到索引的一样,如果只有一部分的索引被读或者更新的话,通常会有更好的性能,因为“活跃”的部分在大多数时间内能驻留在内存中。本文上述的所描述的选择分片Key的方法都是为了最终数据能够均匀的分布,与此同时,每一个Mongod的索引信息也被均匀分布了。相反,使用时间戳作为分片key的起始字段会有利于将常用索引限定在较小的一部分。
  假定有一个图片存储系统,图片记录类似于如下形式:
  {

  _id: ObjectId("4d084f78a4c8707815a601d7"),
  user_id : 42 ,
  title: "sunset at the beach",
  upload_time : "2011-01-02T21:21:56.249Z" ,
  data: ...,
  }
  你也能构造一个客户id,让它包括图片上传时间对应的月度信息和一个唯一标志符(ObjectID,数据的MD5等)。记录看起来就像下面这个样子的:
  {
  _id: "2011-01-02_4d084f78a4c8707815a601d7",

  user_id : 42 ,

  title: "sunset at the beach",

  upload_time : "2011-01-02T21:21:56.249Z" ,

  data: ...,

  }

  客户id作为分片key,并且这个id也被用于去访问这个Document。即能将数据均衡的分布在各个节点上,也减少了大多数查询所使用的索引比例。
  更进一步来讲:
  在每一个月份的开始,在开最初的一段时间内只有一个Server来存取数据,随着数据量的增长,负载均衡器(balancer)就开始进行分裂和迁移数据块了。为了避免潜在的低效率和迁移数据,预先创建分片范围区间是明智之举。(如果有5个Sever则分5个区间)
  可以继续改进,可以把User_ID包含到图片的id中来。这样的话会让一个用户的所有Document都在一个分片上。比如用“2011-01-02_42_4d084f78a4c8707815a601d7”作为图片的id。
  GridFS
  根据需求的不同,GridFS有几种不同的分片方法。基于预先存在的索引是惯用的分片办法:
  1)“files”集合(Collection)不会分片,所有的文件记录都会位于一个分片上,高度推荐使该分片保持高度灵活(至少使用由3个节点构成的replica set)。
  2)“chunks”集合(Collection)应该被分片,并且用索引”files_id:1”。已经存在的由MongoDB的驱动来创建的“files_id,n”索引不能用作分片Key(这个是一个分片约束,后续会被修复),所以不得不创建一个独立的”files_id”索引。使用“files_id”作为分片Key的原因是一个特定的文件的所有Chunks都是在相同的分片上,非常安全并且允许运行“filemd5”命令(要求特定的驱动)。
  运行如下命令:
  > db.fs.chunks.ensureIndex({files_id: 1});

  > db.runCommand({ shardcollection : "test.fs.chunks", key : { files_id : 1 }})

  { "collectionsharded" : "test.fs.chunks", "ok" : 1 }

  由于默认的files_id是一个ObjectId,files_id将会升序增长,因此,GridFS的全部Chunks都会被从一个单点分片上存取。如果写的负载比较高,就需要使用其他的分片Key了,或者使用其它的值(_id)来作为分片Key了。
  
  选择分片Key的需要考虑的因素具有一定的对立性,不可能样样的具备,在实际使用过程中还是需要根据需求的不同来进行权衡,适当放弃一些。没有万能的普适分片办法



查看完整回答
反对 回复 2019-03-25
?
慕仰8121524

Mongodb中一个被分片的Collection的所有数据都存放在众多的Chunk中。一个Chunk存放分片字段的一个区间范围的数据。选择一个好的分片字段非常重要,否则就会遭遇到不能被拆分的大Chunk。
用上述的日志为例,如果选择{server:1}来作为一个分片Key的话,一个server上的所有数据都是在同一个Chunk中,很容易想到一个Server上的日志数据会超过200MB(默认Chunk大小)。如果分片Key是{server:1,time:1},那么能够将一个Server上的日志信息进行分片,直至毫秒级别,绝对不会存在不可被拆分的Chunk。
将Chunk的规模维持在一

查看完整回答
反对 回复 2019-03-25
?
杨魅力

分片是MongoDB提供的一种机制,其可以将大型的集合分割保存到不同的服务器上。与其他的分区方案相比,MongoDB几乎能自动为我们完成所有事情。只要我们进行简单的配置,并告诉MongoDB要分配的数据,它就可以自动维护数据在不同服务器之间的平衡。同时根据需要增减服务器,MongoDB也会自动移动平移已有数据。
分片机制提供了如下三种优势
1. 对集群进行抽象,让集群“不可见”。
MongoDB自带了一个叫做mongos的专有路由进程。mongos就是掌握统一路口的路由器,其会将客户端发来的请求准确无误的路由到集群中的一个或者一组服务器上,同时会把接收到的响应拼装起来发回到客户端。
2.保证集群总是可读写。
MongoDB通过多种途径来确保集群的可用性可靠性。将MongoDB的分片和复制功能结合使用,在确保数据分片到多台服务器的同时,也确保了每分数据都有相应的备份,这样就可以确保有服务器换掉时,其他的副本可以立即接替坏掉的部分继续工作。
3.使集群易于扩展。
当系统需要更多的空间和资源的时候,MongoDB使我们可以按需方便的扩充系统容量。
实现数据分割
分片(shard)是集群中存储集合数据子集的一台或者多台服务器。在生产环境中一个分片通常是一个副本集(replica set)。

片键(key),MongoDB以其作为依据来确定需要在不同分片服务器之间移动的数据。例如我们可以选择用户名(username)字段作为分片键,现有一用户名区间[“p”,”z”],那么wufengtinghai是属于这一区间的,那么数据最终会保存到与此区间对应的分片服务器上。
分配数据到分片服务器
分配数据到分片服务器可以使用不同的方式,了解不同的方式可以加深我们对MongoDB使用方式的理解。
一分片一区间
分配数据到分片最简单的方式莫过于一个区间一个分片。假设我们有四个分片存储用户的相关信息,则我们可能会得到如下的分片和区间的对应关系。

这种分片方式非常简单易懂,但是在一个大型繁忙的系统中却会带来许多的不便。假如大量的用户使用首字母在【“a”,”f”)中的名字来注册,这将会导致分片1比较大,因此需要将其一部分文档移动到分片2上,我们可以调整分片1对应区间【”a”,”c”),使分片2的区间变成【”c”,”n”)。

如果移动数据后,分片2因此过载怎么办?假设分片1和分片2各有500G数据,而分片3和分片4各自有300G数据。那么按照这个方案,最终需要一连串的复制,总共算下来需要移动400G数据,考虑到需要在集群的服务器之间移动这些数据,可见移动数据量之大。

如果需要新加分片服务器进行水平扩展呢?假设此时每个分片上都有了500G数据,那么我们现在需要将分片4上的400G数据移动到分片5,将分片3的300G数据移动到分片4,将分片2的200G数据移动到分片3,将分片1的100G数据移动到分片2,整整移动了1T的数据!

随着分片数量和数据量的增长,这种噩梦将会持续下去,因此MongoDB不会采用这种方式。
一分片多区间
如果我们采用一分片多区间的方式,我们可以将分片1上的数据划分为两个区间,【”a”,”d”)包含400G数据,【”d”,”f”)包含100G数据,同样我们也可以对分片2做类似的处理,得到区间【”f”,”j”)和【“j”,”n”)。现在我们只需要将分片1上的【”d”,”f”)数据移动到分片4,将分片2的【“j”,”n”)的数据移动到分片3。这样我们仅仅只需要移动200G数据。

如果要添加新分片,可以从每个分片顶端取100G数据并将其移动到新的分片上,这样仅仅只需要移动400G数据即可。

MongoDB就是利用这种方式,当一个分片的数据越来越大时,其会自动分割片键区间,并将分片的数据进行分割并移动到其他分片。

 


查看完整回答
反对 回复 2019-03-25

添加回答

回复

举报

0/150
提交
取消
意见反馈 邀请有奖 帮助中心 APP下载
官方微信