zoukankan      html  css  js  c++  java
  • Mongo分片之分片管理

    导航:

    Mongo分片:

      1.Mongo分片介绍

      2.Mongo分片之配置分片

      3.Mongo分片之选择片键

      4.Mongo分片之分片管理

      对数据库管理员来说,分片集群是最困难的部署类型。本章将学习在集群上执行管理任务的方方面面,内容包括:

      • 检査集群状态:集群有哪些成员?数据保存在哪里?哪些连接是打开的?
      • 如何添加、删除和修改集群的成员;
      • 管理数据移动和手动移动数据。

     

    1.检查集群状态

      有一些辅助函数可用于找出数据保存位置、所在分片,以及集群正在进行的操作。

     

    1.1 使用sh.status查看集群摘要信息

      使用sh.status()可査看分片、数据库和分片集合的摘要信息。如果块的数量较少,则该命令会打印出每个块的保存位置。否则它只会简单地给出集合的片键,以及每个分片的块数:

    > sh.status()
    --- Sharding Status --- 
      sharding version: { "_id" : 1, "version" : 3 }
      shards:
        { "_id" : "shard0000", "host" : "localhost:30000", 
          "tags" : [ "USPS" , "Apple" ] }
        {  "_id" : "shard0001",  "host" : "localhost:30001" }
        {  "_id" : "shard0002",  "host" : "localhost:30002",  "tags" : [ "Apple" ] }
      databases:
        {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
        {  "_id" : "test",  "partitioned" : true,  "primary" : "shard0001" }
          test.foo
            shard key: { "x" : 1, "y" : 1 }
            chunks:
              shard0000    4
              shard0002    4
              shard0001    4
            { "x" : { $minKey : 1 }, "y" : { $minKey : 1 } } -->> 
                { "x" : 0, "y" : 10000 } on : shard0000
            { "x" : 0, "y" : 10000 } -->> { "x" : 12208, "y" : -2208 } 
                on : shard0002
            { "x" : 12208, "y" : -2208 } -->> { "x" : 24123, "y" : -14123 } 
                on : shard0000 
            { "x" : 24123, "y" : -14123 } -->> { "x" : 39467, "y" : -29467 } 
                on : shard0002
            { "x" : 39467, "y" : -29467 } -->> { "x" : 51382, "y" : -41382 } 
                on : shard0000
            { "x" : 51382, "y" : -41382 } -->> { "x" : 64897, "y" : -54897 } 
                on : shard0002
            { "x" : 64897, "y" : -54897 } -->> { "x" : 76812, "y" : -66812 } 
                on : shard0000
            { "x" : 76812, "y" : -66812 } -->> { "x" : 92793, "y" : -82793 } 
                on : shard0002
            { "x" : 92793, "y" : -82793 } -->> { "x" : 119599, "y" : -109599 } 
                on : shard0001
            { "x" : 119599, "y" : -109599 } -->> { "x" : 147099, "y" : -137099 } 
                on : shard0001
            { "x" : 147099, "y" : -137099 } -->> { "x" : 173932, "y" : -163932 } 
                on : shard0001
            { "x" : 173932, "y" : -163932 } -->> 
                { "x" : { $maxKey : 1 }, "y" : { $maxKey : 1 } } on : shard0001
          test.ips
            shard key: { "ip" : 1 }
            chunks:
              shard0000    2
              shard0002    3
              shard0001    3
            { "ip" : { $minKey : 1 } } -->> { "ip" : "002.075.101.096" } 
              on : shard0000
            { "ip" : "002.075.101.096" } -->> { "ip" : "022.089.076.022" } 
              on : shard0002 
            { "ip" : "022.089.076.022" } -->> { "ip" : "038.041.058.074" }
              on : shard0002 
            { "ip" : "038.041.058.074" } -->> { "ip" : "055.081.104.118" }
              on : shard0002 
            { "ip" : "055.081.104.118" } -->> { "ip" : "072.034.009.012" }
              on : shard0000 
            { "ip" : "072.034.009.012" } -->> { "ip" : "090.118.120.031" }
              on : shard0001 
            { "ip" : "090.118.120.031" } -->> { "ip" : "127.126.116.125" }
              on : shard0001
            { "ip" : "127.126.116.125" } -->> { "ip" : { $maxKey : 1 } }
              on : shard0001
              tag: Apple { "ip" : "017.000.000.000" } -->> { "ip" : "018.000.000.000" }
              tag: USPS { "ip" : "056.000.000.000" } -->> { "ip" : "057.000.000.000" }
        {  "_id" : "test2",  "partitioned" : false,  "primary" : "shard0002" }

      块的数量较多时,sh.status()命令会概述块的状态,而非打印出每个块的相关信息。如需査看所有的块,可使用sh.status(true)命令(true参数要求sh.status()命令打印出尽可能详尽的信息)。

      sh.status()显示的所有信息都来自config数据库。

      运行sh.status()命令,使MapReduce获取这一数据,因此,如果启动数据库时指定了 --noscripting选项,则无法运行sh.status()命令。

     

    1.2 检查配置信息

      集群相关的所有配置信息都保存在配置服务器上config数据库的集合中。可直接访问该数据库,不过shell提供了一些辅助函数,并通过这些函数获取更适于阅读的信息。不过,可始终通过直接査询config数据库的方式,获取集群的元数据。

      提示:永远不要直接连接到配置服务器,以防配置服务器数据被不小心修改或删除。应先连接到mongos,然后通过config数据库来査询相关信息,方法与查询其他数据库一样:

    mongos> use config

      如果通过mongos操作配置数据(而不是直接连接到配置服务器),mongos会保证将修改同步到所有配置服务器,也会防止危险操作的发生,如意外删除config数据库等。

      

      总的来说,不应直接修改config数据库的任何数据(例外情况下面会提到)。如果确实修改了某些数据,通常需要重启所有的mongos服务器,才能看到效果。

      config数据库中有一些集合,本节将介绍这些集合的内容和使用方法。

     

      1.config.shards

      shards集合跟踪记录集群内所有分片的信息。shards集合中的一个典型文档结构如下:

    > db.shards.findOne()
    {
        "_id" : "spock", 
        "host" : "spock/server-1:27017,server-2:27017,server-3:27017",
        "tags" : [
            "us-east",
            "64gb mem",
            "cpu3"
        ]
    }

      分片的"_id"来自于副本集的名称,所以集群中的每个副本集名称都必须是唯一的。

      更新副本集配置的时候(比如添加或删除成员),host字段会自动更新。

     

      2.config.databases

      databases集合跟踪记录集群中所有数据库的信息,不管数据库有没有被分片:

    > db.databases.find()
    { "_id" : "admin", "partitioned" : false, "primary" : "config" }
    { "_id" : "test1", "partitioned" : true, "primary" : "spock" }
    { "_id" : "test2", "partitioned" : false, "primary" : "bones" }

      如果在数据库上执行过enableSharding,则此处的"partitioned"字段值就是true。"primary"是“主数据库”(home base)。数据库的所有新集合均默认被创建在数据库的主分片上。

     

      3.config.collections

      collections集合跟踪记录所有分片集合的信息(非分片集合信息除外)。其中的文档 结构如下:

    > db.collections.findOne()
    { 
        "_id" : "test.foo", 
        "lastmod" : ISODate("1970-01-16T17:53:52.934Z"), 
        "dropped" : false, 
        "key" : { "x" : 1, "y" : 1 }, 
        "unique" : true 
    }

      下面是一些重要字段。

    • _id

      集合的命名空间。

    • key

      片键。本例中指由x和y组成的复合片键。

    • unique

      表明片键是一个唯一索引。该字段只有当值为true时才会出现(表明片键唯一的)。片键默认不是唯一的

     

      4.config.chunks

      chunks 集合记录有集合中所有块的信息。Chunks集合中的一个典型文档结构如下所示:

    { 
        "_id" : "test.hashy-user_id_-1034308116544453153", 
        "lastmod" : { "t" : 5000, "i" : 50 }, 
        "lastmodEpoch" : ObjectId("50f5c648866900ccb6ed7c88"), 
        "ns" : "test.hashy", 
        "min" : { "user_id" : NumberLong("-1034308116544453153") }, 
        "max" : { "user_id" : NumberLong("-732765964052501510") }, 
        "shard" : "test-rs2" 
    }

      下面这些字段最为有用。

    • _id

      块的唯一标识符。该标识符通常由命名空间、片键和块的下边界值组成。

    • ns

      块所属的集合名称。

    • in

      块范围的最小值(包含)。

    • max

      块范围的最大值(不包含)。

    • shard

      块所属的分片。

      

      这里的lastmod和lastmodEpoch字段用于记录块的版本。例如,如一个名为foo.bar-_id-1的块被拆分为两个块,原本的foo.bar-_id-1会成为一个较小的新块,我们需要一种方式来区别该块与之前的块。因此,我们用t和i字段表示块的主(major)版本和副(minor)版本:主版本会在块被迁移至新的分片时发生改变,副版本会在块被拆分时发生改变。

      sh.status()获取的大部分信息都来自于config.chunks集合。

     

      5.config.changelog

      changelog集合可用于跟踪记录集群的操作,因为该集合会记录所有的拆分和迁移操作。

      拆分记录的文档结构如下:

    { 
        "_id" : "router1-2013-02-09T18:08:12-5116908cab10a03b0cd748c3", 
        "server" : "spock-01", 
        "clientAddr" : "10.3.1.71:62813", 
        "time" : ISODate("2013-02-09T18:08:12.574Z"), 
        "what" : "split", 
        "ns" : "test.foo", 
        "details" : { 
            "before" : { 
                "min" : { "x" : { $minKey : 1 }, "y" : { $minKey : 1 } }, 
                "max" : { "x" : { $maxKey : 1 }, "y" : { $maxKey : 1 } }, 
                "lastmod" : Timestamp(1000, 0), 
                "lastmodEpoch" : ObjectId("000000000000000000000000") 
            }, 
            "left" : { 
                "min" : { "x" : { $minKey : 1 }, "y" : { $minKey : 1 } }, 
                "max" : { "x" : 0, "y" : 10000 }, 
                "lastmod" : Timestamp(1000, 1), 
                "lastmodEpoch" : ObjectId("000000000000000000000000") 
            }, 
            "right" : { 
                "min" : { "x" : 0, "y" : 10000 }, 
                "max" : { "x" : { $maxKey : 1 }, "y" : { $maxKey : 1 } }, 
                "lastmod" : Timestamp(1000, 2), 
                "lastmodEpoch" : ObjectId("000000000000000000000000") 
            } 
        } 
    }

      从details字段中可以看到文档在拆分前和拆分后的内容。

      这里显示的是集合第一个块被拆分后的情景。注意,每个新块的副版本都发生了增长:新块的lastmod分别是Timestamp(1000,1)和Timestamp(1000,2).

      数据迁移的操作比较复杂,每次迁移实际上会创建4个独立的changelog文档:一条是迁移开始时的状态,一条是from分片的文档,一条是to分片的文档,还有一条是迁移完成时的状态。中间的两个文档比较有参考价值,因为可从中看出每一步操作耗时多久。这样就可得知,造成迁移瓶颈的到底是磁盘、网络还是其他什么原因了。

      例如,from分片的文档结构如下:

    { 
         "_id" : "router1-2013-02-09T18:15:14-5116923271b903e42184211c", 
         "server" : "spock-01", 
         "clientAddr" : "10.3.1.71:27017", 
         "time" : ISODate("2013-02-09T18:15:14.388Z"), 
         "what" : "moveChunk.to", 
         "ns" : "test.foo", 
         "details" : { 
             "min" : { "x" : 24123, "y" : -14123 }, 
             "max" : { "x" : 39467, "y" : -29467 }, 
             "step1 of 5" : 0, 
             "step2 of 5" : 0, 
             "step3 of 5" : 900, 
             "step4 of 5" : 0, 
             "step5 of 5" : 142 
         } 
    };

      details字段中的每一步表示的都是时间,stepN of 5信息以毫秒为单位,显示了步骤的耗时长短。当from分片收到mongos发来的moveChunk命令时,它会:

        (1) 检査命令的参数;

        (2) 向配置服务器申请获得一个分布锁,以便进入迁移过程;

        (3) 尝试连接到to分片;

        (4) 数据复制,这是整个过程的“临界区”(critical section);

        (5) 与to分片和配置服务器一起确认迁移是否成功完成。

      注意:step4 of 5中的to和from分片间进行的是直接通信:每个分片都是直接连接到另一个分片和配置服务器上,以进行迁移。如果from分片在迁移过程的最后一步出现短暂的网络连接问题,它可能会处于无法撤销迁移操作也无法继续进行下去的状态。在这种情况下,mongod会关闭。

      to分片的changloe文档与from分片类似,但步骤有些许不同:

    { 
         "_id" : "router1-2013-02-09T18:15:14-51169232ab10a03b0cd748e5", 
         "server" : "spock-01", 
         "clientAddr" : "10.3.1.71:62813", 
         "time" : ISODate("2013-02-09T18:15:14.391Z"), 
         "what" : "moveChunk.from", 
         "ns" : "test.foo", 
         "details" : { 
              "min" : { "x" : 24123, "y" : -14123 }, 
              "max" : { "x" : 39467, "y" : -29467 }, 
              "step1 of 6" : 0, 
              "step2 of 6" : 2, 
              "step3 of 6" : 33, 
              "step4 of 6" : 1032, 
              "step5 of 6" : 12, 
              "step6 of 6" : 0 
         } 
    }

      当to分片收到from分片发来的命令时,它会执行如下操作。

        (1) 迁移索引。如果该分片不包含任何来自迁移集合的块,则需知道有哪些字段上建立过索引。如果在此之前to分片已有来自于该集合的块,则可忽略此步骤。

        (2) 刪除块范围内已存在的任何数据。之前失败的迁移(如果有的话)可能会留有数据残余,或者是正处于恢复过程当中,此时我们不希望残留数据与新数据混杂在一起。

        (3) 将块中的所有文档复制到to分片。

        (4) 复制期间,在to分片上重新执行曾在这些文档上执行过的操作。

        (5) 等待to分片将新迁移过来的数据复制到集群的大多数服务器上。

        (6) 修改块的元数据以完成迁移过程,表明数据已被成功迁移到to分片上。

     

      6.config.tags

      该集合的创建是在为系统配置分片标签时发生的。每个标签都与一个块范围相关联:

    > db.tags.find()
    {
        "_id" : {
            "ns" : "test.ips",
            "min" : {"ip" : "056.000.000.000"}
        },
        "ns" : "test.ips",
        "min" : {"ip" : "056.000.000.000"},
        "max" : {"ip" : "057.000.000.000"},
        "tag" : "USPS"
    }
    {
        "_id" : {
            "ns" : "test.ips",
            "min" : {"ip" : "017.000.000.000"}
        },
        "ns" : "test.ips",
        "min" : {"ip" : "017.000.000.000"},
        "max" : {"ip" : "018.000.000.000"},
        "tag" : "Apple"
    }

      7.config.settings

      该集合含有当前的均衡器设置和块大小的文档信息。通过修改该集合的文档,可开启或关闭均衡器,也可以修改块的大小。注意,应总是连接到mongos修改该集合的值,而不应直接连接到配置服务器进行修改。

     

    2.查看网络连接

      集群的各组成部分间存在大量的连接。本节将学习与分片相关的连接信息。

     

    2.1 查看连接统计

      可使用connPoolStats命令,査看mongos和mongod之间的连接信息,并可得知服务器上打开的所有连接:

    > db.adminCommand({"connPoolStats" : 1})
    {
        "createdByType": {
            "sync": 857,
            "set": 4
        },
        "numDBClientConnection": 35,
        "numAScopedConnection": 0,
        "hosts": {
            "config-01:10005,config-02:10005,config-03:10005": {
                "created": 857,
                "available": 2
            },
            "spock/spock-01:10005,spock-02:10005,spock-03:10005": {
                "created": 4,
                "available": 1
            }
        },
        "totalAvailable": 3,
        "totalCreated": 861,
        "ok": 1
    }

      形如"host1,host2,host3"的主机名是来自配置服务器的连接,也就是用于“同步”的连接。形如"name/host1,host2,...,hostN"的主机是来自分片的连接。available的值表明当前实例的连接池中有多少可用连接。

      注意:只有在分片内的mongos和mongod上运行这个命令才会有效。

      在一个分片上执行connPoolStats,输出信息中可看到该分片与其他分片间的连接,包括连接到其他分片做数据迁移的连接。分片的主连接会直接连接到另一分片的主连接上,然后从目标分片吸取数据。

      进行迁移时,分片会建立一个ReplicaSetMonitor(该进程用于监控副本集的健康状况),用于追踪记录迁移另一端分片的健康状况。由于mongod不会销毁这个监控器,所以有时会在一个副本集的日志中看到其他副本集成员的信息。这是很正常的,不会对应用程序造成任何影响。

     

    2.2 限制连接数量

      当有客户端连接到mongos时,mongos会创建一个连接,该连接应至少连接到一个分片上,以便将客户端请求发送给分片。因此,每个连接到mongos的客户端连接都会至少产生一个从mongos到分片的连接。

      如果有多个mongos进程,可能会创建出非常多的连接,甚至超出分片的处理能力:一个mongos最多允许20 000个连接(mongod也是如此)。如果有5个mongos进程,每个mongos有10 000个客户端连接,那么这些mongos可能会试图创建50 000个到分片的连接!

      为防止这种情况的发生,可在mongos的命令行配置中使用maxConns选项,这样可以限制mongos能够创建的连接数量。可使用下列公式计算分片能够处理的来自单一mongos的连接数量:

      maxConns=20 000 - (mongos进程的数量x3)-(毎个副本集的成员数量x3) -(其他/mongos进程的数量)

      以下为公式的相关说明。

    • (mongos进程的数量x 3)

      每个mongos会为每个mongod创建3个连接:一个用于转发客户端请求,一个用于追踪错误信息,即写回监听器(writeback listener),—个用于监控副本集状态。

    • (每个副本集的成员数量x 3)

      主节点会与每个备份节点创建一个连接,而每个备份节点会与主节点创建两个连接,因此总共是3个连接。

    • (其他/mongos进程的数量)

      这里的其他指其他可能连接到mongod的进程数量,这种连接包括MMS代理、shell的直接连接(管理员用),或者是迁移时连接到其他分片的连接。

      注意:maxConns只会阻止mongos创建多于maxConns数量的连接,但并不会帮助处理连接耗尽的问题。连接耗尽时,请求会发生阻塞,等待某些连接被释放。因此,必须防止应用程序使用超过maxConns数量的连接,尤其是在mongos进程数量不断增加时。

      MongoDB实例在安全退出时,会在终止运行之前关闭所有连接。已经连接到MongoDB的成员会立即收到套接字错误(socket error),并能够重新刷新连接。但是,如果MongoDB实例由于断电、崩溃或者网络问题突然离线,那些已经打开的套接字很可能没有被关闭。在这种情况下,集群内的其他服务器很可能会认为这个MongoDB实例仍在有效运转,但是当试图在该MongoDB实例上执行操作时,就会遇到错误,继而刷新连接(如果此时该MongoDB实例再次上线且运转正常的话)。

      连接数量较少时,可快速检测到某台MongoDB实例是否已离线。但是,当有成千上万个连接时,每个连接都需要经历被尝试、检测失败,并重新建立连接的过程,此过程中会得到大量的错误。在出现大量重新连接时,除了重启进程,没有其他特殊有效的方法。

     

    3.服务器管理

      随着集群的增长,我们可能需要增加集群容量或者是修改集群配置。本节将讲解向集群添加服务器以及从集群删除服务器的方法。

     

    3.1 添加服务器

      可随时向集群中添加新的mongos。只要保证在mongos的--configdb选项中指定了一组正确的配置服务,mongos即可立即与客户端建立连接。

      可使用addShard命令,向集群添加新分片。

     

    3.2 修改分片的服务器

      使用分片集群时,我们可能会希望修改某单独分片的服务器。要修改分片的成员,需直接连接到分片的主服务器上(而不是通过mongos),然后对副本集进行重新配置。集群配置会自动检测更改,并将其更新到config.shards上。不要手动修改config.shards。

      只有在使用单机服务器作为分片,而不是使用副本集作为分片时,才需手动修改config.shards。 

     

      将单机服务器分片修改为副本集分片

      最简单的方式是添加一个新的空副本集分片,然后移除单机服务器分片

      如果希望把单机服务器分片转换为副本集分片,过程会复杂得多,而且需要停机。

        (1) 停止向系统发送请求。

        (2) 关闭单机服务器(这里称其为server-1)和所有的mongos进程。

        (3) 以副本集模式重启server-1 (使用--replSet选项)。

        (4) 连接到server-1,将其作为一个单成员副本集进行初始化。

        (5) 连接到配置服务器,替换该分片的入口,在config.shards中将分片名称替换为 setName/server-1:27017的形式^确保三个配置服务器都拥有相同的配置信息。手动修改配置服务器是有风险的!

          可在每个配置服务器上执行dbhash命令,以确保配置信息相同:

    >  db.runCommand({"dbhash" : 1})

          这样可以得到每个集合的MD5散列值。不同配置服务器上,config数据库的集合可能会有所不同,但config.shards应始终保持一致。

        (6) 重启所有mongos进程。它们会在启动时从配置服务器读取分片数据,然后将副本集当作分片对待。

        (7) 重启所有分片的主服务器,刷新其配置数据。

        (8) 再次向系统发送请求。

        (9) 向server-1副本集中添加新成员。

      这一过程非常复杂,而且很容易出错,因此不建议使用。应尽可能地将空的副本集作为新的分片添加到集群中,数据迁移的事情交给集群去做就好了。

     

    3.3 删除分片

      通常来说,不应从集群中删除分片。如果经常在集群中添加和删除分片,会给系统带来很多不必要的压力。如果向集群中添加了过多的分片,最好是什么也不做,系统早晚会用到这些分片,而不应该将多余的分片删掉,等以后需要的时候再将其重新添加到集群中。不过,在必要的情况下,是可以删除分片的。

      首先保证均衡器是开启的。在排出数据(draining)的过程中,均衡器会负责将待删除分片的数据迁移至其他分片。执行removeShard命令,开始排出数据。removeShard将待删除分片的名称作为参数,然后将该分片上的所有块都移至其他分片上:

    > db.adminCommand({"removeShard" : "test-rs3"})
    {
        "msg" : "draining started successfully",
        "state" : "started",
        "shard" : "test-rs3",
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [
            "blog",
            "music",
            "prod"
        ],
        "ok" : 1
    }

      如果分片上的块较多,或者有较大的块需要移动,排出数据的过程可能会耗时更长。 如果存在特大块(jumbo chunk),可能需临时提高其他分片的块大小,以便能够将特大块迁移到其他分片。

      如需査看哪些块已完成迁移,可再次执行removeShard命令,查看当前状态:

    > db.adminCommand({"removeShard" : "test-rs3"})
    {
        "msg" : "draining ongoing",
        "state" : "ongoing",
        "remaining" : {
            "chunks" : NumberLong(5),
            "dbs" : NumberLong(0)
        },
        "ok" : 1
    }

      在一个处于排出数据过程的分片上,可执行removeShard任意多次。

      块在移动前可能需要被拆分,所以有可能会看到系统中的块数量在排出数据时发生了增长。假设有一个拥有5个分片的集群,块的分布如下:

    test-rs0 10
    test-rs1 10
    test-rs2 10
    test-rs3 11
    test-rs4 11

      该集群共有52个块。如果删除test-rs3分片,最终的结果可能会是:

    test-rs0 15
    test-rs1 15
    test-rs2 15
    test-rs4 15

      集群现在拥有60个块,其中18个来自test-rs3分片(原本有11个,还有7个是在 排出数据的过程中创建的)。

      所有的块都完成迁移后,如果仍有数据库将该分片作为主分片,需在删除分片前将这些数据库移除掉。removeShard命令的输出结果可能如下:

    > db.adminCommand({"removeShard" : "test-rs3"})
    {
        "msg" : "draining ongoing",
        "state" : "ongoing",
        "remaining" : {
            "chunks" : NumberLong(0),
            "dbs" : NumberLong(3)
        },
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [
            "blog",
            "music",
            "prod"
        ],
        "ok" : 1
    }

      为完成分片的删除,需先使用movePrimary命令将这些数据库移走:

    > db.adminCommand({"movePrimary" : "blog", "to" : "test-rs4"})
    {
        "primary " : "test-rs4:test-rs4/ubuntu:31500,ubuntu:31501,ubuntu:31502",
        "ok" : 1
    }

      然后再次执行removeShard命令:

    > db.adminCommand({"removeShard" : "test-rs3"})
    {
        "msg" : "removeshard completed successfully",
        "state" : "completed",
        "shard" : "test-rs3",
        "ok" : 1
    }

      最后一步不是必需的,但可确保已确实完成了分片的删除。如果不存在将该分片作为主分片的数据库,则块的迁移完成后,即可看到分片删除成功的输出信息。

      注意,如果分片开始排出数据,就没有内置办法停止这一过程了。

     

    3.4 修改配置服务器

      修改配置服务器是非常困难的,而且有风险,通常还需要停机。注意,修改配置服务器前,应做好备份。

      在运行期间,所有mongos进程的--configdb选项值都必须相同。因此,要修改配置服务器,首先必须关闭所有的mongos进程(mongos进程在使用旧的--configdb参数时,无法继续保持运行状态),然后使用新的--configdb参数重启所有mongos进程。

      例如,将一台配置服务器增至三台是最常见的任务之一。为实现此操作,首先应关闭所有的mongos进程、配置服务器,以及所有的分片。然后将配置服务器的数据目录复制到两台新的配置服务器上(这样三台配置服务器就可以拥有完全相同的数据目录)。接着,启动这三台配置服务器和所有分片。然后,将--configdb选项指定为这三台配置服务器,最后重启所有的mongos进程。

     

    4.数据均衡

      通常来说,MongoDB会自动处理数据均衡。本节将学习如何启用和禁用自动均衡,以及如何人为干涉均衡过程。

     

    4.1 均衡器

      在执行几乎所有的数据库管理操作之前,都应先关闭均衡器。可使用下列shell辅助函数关闭均衡器:

    > sh.setBalancerState(false)

      均衡器关闭后,系统则不会再进入均衡过程,但该命令并不能立即终止进行中的均衡过程:迁移过程通常无法立即停止。因此,应检査config.locks集合,以查看均衡过程是否仍在进行中:

    >db.locks.find({"_id" : "balancer"})["state"]
    0

      此处的0表明均衡器已被关闭。

      均衡过程会增加系统负载:目标分片必须査询源分片块中的所有文档,将文档插入目标分片的块中,源分片最后必须删除这些文档。在以下两种特殊情况下,迁移会导致性能问题。

        (1) 使用热点片键可保证定期迁移(因为所有的新块都是创建在热点上的)。系统必须有能力处理源源不断写入到热点分片上的数据。

        (2) 向集群中添加新的分片时,均衡器会试图为该分片写入数据,从而触发一系列的迁移过程。

      如果发现数据迁移过程影响了应用程序性能,可在config.settings集合中为数据均 衡指定一个时间窗口。执行下列更新语句,均衡则只会在下午1点到4点间发生:

    > db.settings.update({"_id" : "balancer"}, 
    ... {"$set" : {"activeWindow" : {"start" : "13:00", "stop" : "16:00"}}}, 
    ... true )

      如指定了均衡时间窗,则应对其进行严密监控,以确保mongos确实只在指定的时间内做均衡。

      如需混用手动均衡和自动均衡,必须格外小心。因为自动均衡器总是根据数据集的当前状态来决定数据迁移,而不考虑数据集的历史状态。例如,假设有两个分片shardA和shardB,每个分片都有500个块。由于shardA上的写请求比较多,因此我们关闭了均衡器,从最活跃的块中取出30个移至shardB。此时如再启用均衡器,则会立即将30个块(很可能不是刚刚的30块)从shardB移至shardA,以均衡两个分片拥有的块数量。

      为防止这种情况发生,可在启用均衡器之前从shardB选取30个不活跃的块移至 shardA。这样两个分片间就不会存在不均衡,均衡器也不会进行数据块的移动了。另外,也可在shardA上拆分出一些块,以实现shardA和shardB的均衡。

      注意:均衡器只使用块的数量,而非数据大小,作为衡量分片间是否均衡的指标。 因此,如果A分片只拥有几个较大的数据块,而B分片拥有许多较小的块(但总数据大小比A小),那么均衡器会将B分片的一些块移至A分片,从而实现均衡。

     

    4.2 修改块大小

      块中的文档数量可能为0,也可能多达数百万。通常情况下,块越大,迁移至分片的耗时就越长。有案例使用的是1 MB的块,所以块移动起来非常容易与迅速。但在实际系统中,这通常是不现实的。MongoDB需要做大量非必要的工作,才能将分片大小维持在几MB以内。块的大小默认为64 MB,这个大小的块既易于迁移,又不会导致过多的流失。

      有时可能会发现移动64 MB的块耗时过长。可通过减小块的大小,提高迁移速度。使用shell连接到mongos,然后修改config.settings集合,从而完成块大小的修改:

    > db.settings.findOne()
    {
        "_id" : "chunksize", 
        "value" : 64 
    }
    > db.settings.save({"_id" : "chunksize", "value" : 32})

      以上修改操作将块的大小减至32 MB。已经存在的块不会立即发生改变,执行块拆分操作时,这些块即可拆分成32 MB大小。mongos进程会自动加载新的块大小。

      注意:该设置的有效范围是整个集群:它会影响所有集合和数据库。因此,如需对一个集合使用较小的块,而对另一集合使用较大的块,比较好的解决方式是取一个折中的值(或者将这两个集合放在不同的集群中)。

      如果MongoDB频繁进行数据迁移或文档较大,则可能需要增加块的大小。

     

    4.3 移动块

      如前所述,同一块内的所有数据都位于同一分片上。如该分片的块数量比其他分片多,则MongoDB会将其中的一部分块移至其他块数量较少的分片上。移动块的过程叫做迁移(migration), MongoDB就是这样在集群中实现数据均衡的。

      可在shell中使用moveChunk辅助函数,手动移动块:

    > sh.moveChunk("test.users", {"user_id" : NumberLong("1844674407370955160")}, 
    ... "spock") 
    { "millis" : 4079, "ok" : 1 }

      以上命令会将包含文档user_id为1844674407370955160的块移至名为spock的分片上。必须使用片键来找出所需移动的块(本例中的片键是user_id)。通常,指定一个块最简单的方式是指定它的下边界,不过指定块范围内的任何值都可以(块的上边界值除外,因为其并不包含在块范围内)。该命令在块移动完成后才会返回,因此需一定耗时才能看到输出信息。

      如某个操作耗时较长,可在日志中详细査看问题所在。如某个块的大小超出了系统指定的最大值,mongos则会拒绝移动这个块:

    > sh.moveChunk("test.users", {"user_id" : NumberLong("1844674407370955160")}, 
    ... "spock")
    {
        "cause" : {
            "chunkTooBig" : true,
            "estimatedChunkSize" : 2214960,
            "ok" : 0,
            "errmsg" : "chunk too big to move"
        },
        "ok" : 0,
        "errmsg" : "move failed"
    }

      本例中,移动这个块之前,必须先手动拆分这个块。可使用splitAt命令对块进行拆分:

    > db.chunks.find({"ns" : "test.users", 
    ... "min.user_id" : NumberLong("1844674407370955160")})
    {
        "_id" : "test.users-user_id_NumberLong("1844674407370955160")", 
        "ns" : "test.users", 
        "min" : { "user_id" : NumberLong("1844674407370955160") }, 
        "max" : { "user_id" : NumberLong("2103288923412120952") }, 
        "shard" : "test-rs2" 
    }
    > sh.splitAt("test.ips", {"user_id" : NumberLong("2000000000000000000")})
    { "ok" : 1 }
    > db.chunks.find({"ns" : "test.users", 
    ... "min.user_id" : {"$gt" : NumberLong("1844674407370955160")},
    ... "max.user_id" : {"$lt" : NumberLong("2103288923412120952")}})
    { 
        "_id" : "test.users-user_id_NumberLong("1844674407370955160")", 
        "ns" : "test.users", 
        "min" : { "user_id" : NumberLong("1844674407370955160") }, 
        "max" : { "user_id" : NumberLong("2000000000000000000") }, 
        "shard" : "test-rs2" 
    }
    { 
        "_id" : "test.users-user_id_NumberLong("2000000000000000000")", 
        "ns" : "test.users", 
        "min" : { "user_id" : NumberLong("2000000000000000000") }, 
        "max" : { "user_id" : NumberLong("2103288923412120952") }, 
        "shard" : "test-rs2" 
    }

      块被拆分为较小的块后,就可以被移动了。也可以调高最大块的大小,然后再移动这个较大的块。不过应尽可能地将大块拆分为小块。不过有时有些块无法被拆分,这些块被称作特大块。

     

    4.4 特大块

      假设使用date字段作为片键。集合中的date字段是一个日期字符串,格式为year/month/day,也就是说,mongos—天最多只能创建一个块。最初的一段时间内一切正常,直到有一天,应用程序的业务量突然出现病毒式增长,流量比平常大了上千倍!

      这一天的块要比其他日期的大得多,但由于块内所有文档的片键值都是一样的,因此这个块是不可拆分的。

      如果块的大小超出了config.settings中设置的最大块大小,那么均衡器就无法移动 这个块了。这种不可拆分和移动的块就叫做特大块,这种块相当难对付。

      举例来说,假如有3个分片shard1、shard2和shard3。如果使用热点片键模式,假设shard1是热点片键,则所有写请求都会被分发到shard1上。mongos会试图将块均衡地分发在这些分片上。但是,均衡器只能移动非特大块,因此它只会将所有较小块从热点分片迁移到其他分片。

      现在,所有分片上的块数基本相同,但shard2和shard3上的所有块都小于64MB。如shard1上出现了特大块,则shard1上会有越来越多的块大于64MB。这样,即使三个分片的块数非常均衡,但shard1会比另两个分片更早被填满。

      出现特大块的表现之一是,某分片的大小增长速度要比其他分片快得多。也可使用sh.status()来检查是否出现了特大块:特大块会存在一个jumbo属性。

    > sh.status()
    ...
        { "x" : -7 } -->> { "x" : 5 } on : shard0001 
        { "x" : 5 } -->> { "x" : 6 } on : shard0001 jumbo
        { "x" : 6 } -->> { "x" : 7 } on : shard0001 jumbo
        { "x" : 7 } -->> { "x" : 339 } on : shard0001
    ...

      可使用dataSize命令检查块大小。

      首先,使用config.chunks集合,查看块范围:

    > use config
    > var chunks = db.chunks.find({"ns" : "acme.analytics"}).toArray()

      然后根据这些块范围,找出可能的特大块:

    > use dbName
    > db.runCommand({"dataSize" : "dbName.collName",
    ... "keyPattern" : {"date" : 1}, // shard key
    ... "min" : chunks[0].min, 
    ... "max" : chunks[0].max})
    { "size" : 11270888, "numObjects" : 128081, "millis" : 100, "ok" : 1 }

      但要小心,因为dataSize命令要扫描整个块的数据才能知道块的大小。因此如果可能,应首先根据自己对数据的了解,尽可能缩小搜索范围:特大块是在特定日期出现的吗?例如,如果11月1号的时候系统非常繁忙,则可尝试检查这一天创建的块的片键范围。如使用了GridFS,而且是依据files_id字段进行分片的,则可通过fs.files集合查看文件大小。

     

      1.分发特大块

      为修复由特大块引发的集群不均衡,就必须将特大块均衡地分发到其他分片上。

      这是一个非常复杂的手动过程,而且不应引起停机(可能会导致系统变慢,因为要迁移大量的数据)。接下来,我们以from分片来指代拥有特大块的分片,以to分片来指代特大块即将移至的目标分片。注意,如有多个from分片,则需对每个from分片重复下列步骤:

      (1) 关闭均衡器,以防其在这一过程中出来捣乱:

    >  sh.setBalancerState(false)

      (2) MongoDB不允许移动大小超出最大块大小设定值的块,所以需临时调高最大块大小的设定值。记下特大块的大小,然后将最大块大小设定值调整为比特大块大一些的数值,比如10000。块大小的单位是MB:

    > use config
    > db.settings.findOne({"_id" : "chunksize"})
    {
        "_id" : "chunksize", 
        "value" : 64
    }
    > db.settings.save({"_id" : "chunksize", "value" : 10000})

      (3) 使用moveChunk命令将特大块从from分片移至to分片。如担心迁移会对应用程序的性能造成影响,可使用secondaryThrootle选项,放慢迁移的过程,减缓对系统性能的影响:

    > db.adminCommand({"moveChunk" : "acme.analytics", 
    ... "find" : {"date" : new Date("10/23/2012")}, 
    ... "to" : "shard0002", 
    ... "secondaryThrottle" : true})

      secondaryThrottle会强制要求迁移过程间歇进行,每迁移完一些数据,需等待集群中的大多数分片成功完成数据复制后再进行下一次迁移。该选项只有在使用副本集分片时才会生效。如使用单机服务器分片,则该选项不会生效。

      (4) 使用splitChunk命令对from分片剩余的块进行拆分,这样可以增加from分片的块数,直到实现from分片与其他分片块数的均衡。

      (5) 将块大小修改回最初值:

    >  db.settings.save({"_id" : "chunksize", "value" : 64})

      (6) 启用均衡器。

    >  sh.setBalancerState(true)

      均衡器被再次启用后,仍旧不能移动特大块,不过此时那些特大块都已位于合适的位置了。

     

      2.防止出现特大块

      随着存储数据量的增长,上一节提到的手动过程变得不再可行。因此,如在特大块方面存在问题,应首先想办法避免特大块的出现。

      为防止特大块的出现,可修改片键,细化片键的粒度。应尽可能保证每个文档都拥有唯一的片键值,或至少不要出现某个片键值的数据块超出最大块大小设定值的情况。

      例如,如使用前面所述的年/月/日片键,可通过添加时、分、秒来细化片键粒度。类似地,如使用粒度较大的片键,如日志级別,则可添加一个粒度较细的字段作为片键的第二个字段,如MD5散列值或UDID。这样一来,即使有许多文档片键的第一个字段值是相同的,也可一直对块进行拆分,也就防止了特大块的出现。

     

    4.5 刷新配置

      最后一点,mongos有时无法从配置服务器正确更新配置。如发现配置有误,mongos的配置过旧或无法找到应有数据,可使用flushRouterConfig命令手动刷新所有缓存:

    > db.adminCommand({"flushRouterConfig" : 1})

      如flushRouterConfig命令没能解决问题,则应重启所有的mongos或mongod 进程,以便清除所有可能的缓存。

     

    作者:小家电维修

    相见有时,后会无期。

  • 相关阅读:
    对于匿名对象,内部类这块的理解
    final等关键字和代码块
    构造方法
    接口与多态的总结
    关于折半法查找的一些总结以及ArrayList类的总结
    关于冒泡法的总结(主要是格式问题了)
    java读取properties文件的几种方式(转载)
    JAVA导出EXCEL表格(转载)
    map遍历的四种方法
    Java集合源码分析(四)HashMap
  • 原文地址:https://www.cnblogs.com/lizexiong/p/15389290.html
Copyright © 2011-2022 走看看