首先还是上图看看mongodb通过哪些机制实现路由、分片:

fenpian3.png


从图中可以看到有四个组件:mongos、config server、shard、replica set。

mongos,数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器,mongos自己就是一个请求分发中心,它负责把对应的数据请求请求转发到对应的shard服务器上。在生产环境通常有多mongos作为请求的入口,防止其中一个挂掉所有的mongodb请求都没有办法操作。

config server,顾名思义为配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,这个可不能丢失!就算挂掉其中一台,只要还有存货, mongodb集群就不会挂掉。

shard,这就是传说中的分片了。上面提到一个机器就算能力再大也有天花板,就像军队打仗一样,一个人再厉害喝血瓶也拼不过对方的一个师。俗话说三个臭皮匠顶个诸葛亮,这个时候团队的力量就凸显出来了。在互联网也是这样,一台普通的机器做不了的多台机器来做,如下图:

fenpian4.png

一台机器的一个数据表 Collection1 存储了 1T 数据,压力太大了!在分给4个机器后,每个机器都是256G,则分摊了集中在一台机器的压力。也许有人问一台机器硬盘加大一点不就可以了,为什么要分给四台机器呢?不要光想到存储空间,实际运行的数据库还有硬盘的读写、网络的IO、CPU和内存的瓶颈。在mongodb集群只要设置好了分片规则,通过mongos操作数据库就能自动把对应的数据操作请求转发到对应的分片机器上。在生产环境中分片的片键可要好好设置,这个影响到了怎么把数据均匀分到多个分片机器上,不要出现其中一台机器分了1T,其他机器没有分到的情况,这样还不如不分片!

replica set,上两节已经详细讲过了这个东东,怎么这里又来凑热闹!其实上图4个分片如果没有 replica set 是个不完整架构,假设其中的一个分片挂掉那四分之一的数据就丢失了,所以在高可用性的分片架构还需要对于每一个分片构建 replica set 副本集保证分片的可靠性。生产环境通常是 2个副本 + 1个仲裁。

说了这么多,还是来实战一下如何搭建高可用的mongodb集群:

首先确定各个组件的数量,mongos 3个, config server 3个,数据分3片 shard server 3个,每个shard 有一个副本一个仲裁也就是 3 * 2 = 6 个,总共需要部署15个实例。这些实例可以部署在独立机器也可以部署在一台机器,我们这里测试资源有限,只准备了 3台机器,在同一台机器只要端口不同就可以,看一下物理部署图:

mongodb分片架构.jpg

  • 1、准备机器,IP分别设置为: 192.168.5.100、192.168.5.101、192.168.5.102。

    2、前期准备『3台机器均需要操作』

  • #分别创建mongodbtest主目录和存放mongodb的执行程序目录
    mkdir -p /data/mongodbtest /data/mongodbtest/bin
    #进到执行目录解压mongodb压缩包并重新命名
    cd /data/mongodbtest/bin
    tar -zxvf mongodb-linux-x86_64-2.6.1.tgz
    mv mongodb-linux-x86_64-2.6.1/ mongodb-2.6.1/
    #把环境变量添加到/etc/profile文件中
    export PATH=$PATH:/data/mongodbtest/bin/mongodb-2.6.1/bin/
    #使配置生效
    source /etc/profile

    3、中期准备『3台机器均需要操作』

  • #创建mongos日志文件
    mkdir -p /data/mongodbtest/mongos/log
    #建立config server 数据文件存放目录和日志目录
    mkdir -p /data/mongodbtest/config/data /data/mongodbtest/config/log
    #建立shard1 数据文件存放目录和日志目录
    mkdir -p /data/mongodbtest/shard1/data /data/mongodbtest/shard1/log
    #建立shard2 数据文件存放目录和日志目录
    mkdir -p /data/mongodbtest/shard2/data /data/mongodbtest/shard2/log
    #建立shard3 数据文件存放目录和日志目录
    mkdir -p /data/mongodbtest/shard3/data /data/mongodbtest/shard3/log

    4、中期准备『规划端口』

  • 由于一个机器需要同时部署 mongos、config server 、shard1、shard2、shard3,所以需要用端口进行区分。
    这个端口可以自由定义,在本文 mongos为 20000, config server 为 21000, shard1为 22001 , shard2为22002, shard3为22003.

    5、按顺序启动对应服务『服务启动』

  • #分别启动3台机器的conf server
    mongod --configsvr --dbpath /data/mongodbtest/config/data --port 21000 --logpath /data/mongodbtest/config/log/config.log --fork
    #分别启动3台机器的mongos
    mongos --configdb 192.168.5.100:21000,192.168.5.101:21000,192.168.5.102:21000  --port 20000   --logpath /data/mongodbtest/mongos/log/mongos.log --fork
    #配置share1的副本集,为了快速启动并节约测试环境存储空间,这里加上 nojournal 是为了关闭日志信息,在我们的测试环境不需要初始化这么大的redo日志。同样设置 oplogsize是为了降低 local 文件的大小,oplog是一个固定长度的 capped collection,它存在于”local”数据库中,用于记录Replica Sets操作日志。注意,这里的设置是为了测试
    mongod --shardsvr --replSet shard1 --port 22001 --dbpath /data/mongodbtest/shard1/data  --logpath /data/mongodbtest/shard1/log/shard1.log --fork --nojournal  --oplogSize 10
    #配置share2的副本集,为了快速启动并节约测试环境存储空间,这里加上 nojournal 是为了关闭日志信息,在我们的测试环境不需要初始化这么大的redo日志。同样设置 oplogsize是为了降低 local 文件的大小,oplog是一个固定长度的 capped collection,它存在于”local”数据库中,用于记录Replica Sets操作日志。注意,这里的设置是为了测试
    mongod --shardsvr --replSet shard2 --port 22002 --dbpath /data/mongodbtest/shard2/data  --logpath /data/mongodbtest/shard2/log/shard2.log --fork --nojournal  --oplogSize 10
    #配置share3的副本集,为了快速启动并节约测试环境存储空间,这里加上 nojournal 是为了关闭日志信息,在我们的测试环境不需要初始化这么大的redo日志。同样设置 oplogsize是为了降低 local 文件的大小,oplog是一个固定长度的 capped collection,它存在于”local”数据库中,用于记录Replica Sets操作日志。注意,这里的设置是为了测试
    mongod --shardsvr --replSet shard3 --port 22003 --dbpath /data/mongodbtest/shard3/data  --logpath /data/mongodbtest/shard3/log/shard3.log --fork --nojournal  --oplogSize 10

    6、配置每个shard副本集『配置副本集

  • #登录192.168.5.100的shard1 mongo实例设置share1 副本集
    mongo 192.168.5.100:22001
    use admin
    config = { _id:"shard1", members:[
                         {_id:0,host:"192.168.5.100:22001"},
                         {_id:1,host:"192.168.5.101:22001"},
                         {_id:2,host:"192.168.5.102:22001",arbiterOnly:true}
                    ]
             }
    #初始化副本集配置
    rs.initiate(config);
    
    #登录192.168.5.100的shard2 mongo实例设置share2 副本集
    mongo 192.168.5.100:22002
    use admin
    config = { _id:"shard2", members:[
                         {_id:0,host:"192.168.5.100:22002"},
                         {_id:1,host:"192.168.5.101:22002"},
                         {_id:2,host:"192.168.5.102:22002",arbiterOnly:true}
                    ]
             }
    #初始化副本集配置
    rs.initiate(config);
    
    
    #登录192.168.5.100的shard3 mongo实例设置share3 副本集
    连接 mongo 192.168.5.100:22003
    use admin
    config = { _id:"shard3", members:[
                         {_id:0,host:"192.168.5.100:22003"},
                         {_id:1,host:"192.168.5.101:22003"},
                         {_id:2,host:"192.168.5.102:22003",arbiterOnly:true}
                    ]
             }
    #初始化副本集配置
    rs.initiate(config);

    7、目前搭建了mongodb配置服务器、路由服务器,各个分片服务器,不过应用程序连接到 mongos 路由服务器并不能使用分片机制,还需要在程序里设置分片配置,让分片生效。

#连接mongos
mongo 127.0.0.1:20000
#注意 这里要加上逗号
user  admin;
#串联路由器与分片副本集1
db.runCommand( { addshard:"shard1/192.168.5.100:22001,192.168.5.101:22001,192.168.5.102:22001"});
#串联路由器与分片副本集2
user  admin;
db.runCommand( { addshard:"shard2/192.168.5.100:22002,192.168.5.101:22002,192.168.5.102:22002"});
#串联路由器与分片副本集3
user  admin;
db.runCommand( { addshard:"shard3/192.168.5.100:22003,192.168.5.101:22003,192.168.5.102:22003"});

#查看分片服务器的配置
db.runCommand( { listshards : 1 } );
#内容输出
{
         "shards" : [
                {
                        "_id" : "shard1",
                        "host" : "shard1/192.168.5.100:22001,192.168.5.101:22001"
                },
                {
                        "_id" : "shard2",
                        "host" : "shard2/192.168.5.100:22002,192.168.5.101:22002"
                },
                {
                        "_id" : "shard3",
                        "host" : "shard3/192.168.5.100:22003,192.168.5.101:22003"
                }
        ],
        "ok" : 1
}

  • 因为192.168.5.103是每个分片副本集的仲裁节点,所以在上面结果没有列出来。

  • 8、目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,但我们的目的是希望插入数据,数据能够自动分片,就差那么一点点,一点点。。。

  • 连接在mongos上,准备让指定的数据库、指定的集合分片生效。

  • #指定testdb分片生效
    db.runCommand( { enablesharding :"testdb"});
    #指定数据库里需要分片的集合和片键
    db.runCommand( { shardcollection :"testdb.table1",key : {id: 1} } )

    9、测试循环插入200000条数据,查看效果。

  • for (var i = 1; i <= 200000; i++) db.table1.save({id:i,"test1":"testval1"})

    10、查看效果

  • mongos>  db.printShardingStatus() db.printShardingStatus()
    --- Sharding Status --- 
      sharding version: {
            "_id" : 1,
            "version" : 4,
            "minCompatibleVersion" : 4,
            "currentVersion" : 5,
            "clusterId" : ObjectId("5c79632ca881001461d466cb")
    }
      shards:
            {  "_id" : "shard1",  "host" : "shard1/192.168.5.100:22001,192.168.5.101:22001" }
            {  "_id" : "shard2",  "host" : "shard2/192.168.5.100:22002,192.168.5.101:22002" }
            {  "_id" : "shard3",  "host" : "shard3/192.168.5.100:22003,192.168.5.101:22003" }
      databases:
            {  "_id" : "admin",  "partitioned" : false,  "primary" : "config" }
            {  "_id" : "testdb",  "partitioned" : true,  "primary" : "shard1" }
                    testdb.table1
                            shard key: { "id" : 1 }
                            chunks:
                                    shard3  5
                                    shard1  5
                                    shard2  6
                            #这里就是前面文章说的chunk了        
                            { "id" : { "$minKey" : 1 } } -->> { "id" : 1 } on : shard3 Timestamp(8, 0) 
                            { "id" : 1 } -->> { "id" : 4836 } on : shard3 Timestamp(11, 0) 
                            { "id" : 4836 } -->> { "id" : 79734 } on : shard1 Timestamp(11, 1) 
                            { "id" : 79734 } -->> { "id" : 155925 } on : shard3 Timestamp(5, 1) 
                            { "id" : 155925 } -->> { "id" : 267774 } on : shard3 Timestamp(4, 2) 
                            { "id" : 267774 } -->> { "id" : 435549 } on : shard1 Timestamp(6, 2) 
                            { "id" : 435549 } -->> { "id" : 603325 } on : shard1 Timestamp(6, 4) 
                            { "id" : 603325 } -->> { "id" : 687213 } on : shard3 Timestamp(12, 0) 
                            { "id" : 687213 } -->> { "id" : 771101 } on : shard2 Timestamp(12, 1) 
                            { "id" : 771101 } -->> { "id" : 943497 } on : shard2 Timestamp(8, 2) 
                            { "id" : 943497 } -->> { "id" : 1093292 } on : shard2 Timestamp(11, 4) 
                            { "id" : 1093292 } -->> { "id" : 1168190 } on : shard2 Timestamp(11, 6) 
                            { "id" : 1168190 } -->> { "id" : 1279045 } on : shard2 Timestamp(11, 7) 
                            { "id" : 1279045 } -->> { "id" : 1624908 } on : shard2 Timestamp(9, 2) 
                            { "id" : 1624908 } -->> { "id" : 1949761 } on : shard1 Timestamp(11, 2) 
                            { "id" : 1949761 } -->> { "id" : { "$maxKey" : 1 } } on : shard1 Timestamp(11, 3) 
            {  "_id" : "test",  "partitioned" : false,  "primary" : "shard1" }
            {  "_id" : "dbtest",  "partitioned" : false,  "primary" : "shard1" }
            {  "_id" : "noshare",  "partitioned" : false,  "primary" : "shard3" }
            {  "_id" : "noshare2",  "partitioned" : true,  "primary" : "shard3" }
                    noshare2.table2
                            shard key: { "id" : 1 }
                            chunks:
                                    shard2  1
                                    shard3  2
                                    shard1  1
                            { "id" : { "$minKey" : 1 } } -->> { "id" : 100000 } on : shard2 Timestamp(3, 0) 
                            { "id" : 100000 } -->> { "id" : 137449 } on : shard3 Timestamp(3, 2) 
                            { "id" : 137449 } -->> { "id" : 200000 } on : shard3 Timestamp(3, 3) 
                            { "id" : 200000 } -->> { "id" : { "$maxKey" : 1 } } on : shard1 Timestamp(2, 0) 
            {  "_id" : "conf",  "partitioned" : false,  "primary" : "shard3" }