按照上一节中《搭建高可用mongodb集群(三)—— 深入副本集》搭建后还有两个问题没有解决:

  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 数据压力大到机器支撑不了的时候能否做到自动扩展?

在系统早期,数据量还小的时候不会引起太大的问题,但是随着数据量持续增多,后续迟早会出现一台机器硬件瓶颈问题的。而mongodb主打的就是海量数据架构,他不能解决海量数据怎么行!不行!“分片”就用这个来解决这个问题。

传统数据库怎么做海量数据读写?其实一句话概括:分而治之。上图看看就清楚了,如下 taobao岳旭强在infoq中提到的 架构图:

fenpian1

上图中有个TDDL,是taobao的一个数据访问层组件,他主要的作用是SQL解析、路由处理。根据应用的请求的功能解析当前访问的sql判断是在哪个业务数据库、哪个表访问查询并返回数据结果。具体如图:

fenpian2

说了这么多传统数据库的架构,那Nosql怎么去做到了这些呢?mysql要做到自动扩展需要加一个数据访问层用程序去扩展,数据库的增加、删除、备份还需要程序去控制。一但数据库的节点一多,要维护起来也是非常头疼的。不过mongodb所有的这一切通过他自己的内部机制就可以搞定!顿时石化了,这么牛X!还是上图看看mongodb通过哪些机制实现路由、分片:

fenpian3

从图中可以看到有四个组件: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

一台机器的一个数据表 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台机器,在同一台机器只要端口不同就可以,看一下物理部署图:

fenpian5

架构搭好了,安装软件!

  • 1、准备机器,IP分别设置为: 192.168.0.136、192.168.0.137、192.168.0.138。
  • 2、分别在每台机器上建立mongodb分片对应测试文件夹。
    #存放mongodb数据文件
    mkdir -p /data/mongodbtest
    
    #进入mongodb文件夹
    cd  /data/mongodbtest
    
  • 3、下载mongodb的安装程序包
    wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz
    
    
    #解压下载的压缩包
    tar xvzf mongodb-linux-x86_64-2.4.8.tgz
    
  • 4、分别在每台机器建立mongos 、config 、 shard1 、shard2、shard3 五个目录。
    因为mongos不存储数据,只需要建立日志文件目录即可。

               #建立mongos目录
               mkdir -p /data/mongodbtest/mongos/log
    
               #建立config server 数据文件存放目录
               mkdir -p /data/mongodbtest/config/data
    
               #建立config server 日志文件存放目录
               mkdir -p /data/mongodbtest/config/log
    
               #建立config server 日志文件存放目录
               mkdir -p /data/mongodbtest/mongos/log
    
               #建立shard1 数据文件存放目录
               mkdir -p /data/mongodbtest/shard1/data
    
               #建立shard1 日志文件存放目录
               mkdir -p /data/mongodbtest/shard1/log
    
               #建立shard2 数据文件存放目录
               mkdir -p /data/mongodbtest/shard2/data
    
    #建立shard2 日志文件存放目录
    mkdir -p /data/mongodbtest/shard2/log
    
    #建立shard3 数据文件存放目录
    mkdir -p /data/mongodbtest/shard3/data
    
    #建立shard3 日志文件存放目录
    mkdir -p /data/mongodbtest/shard3/log
    
  • 5、规划5个组件对应的端口号,由于一个机器需要同时部署 mongos、config server 、shard1、shard2、shard3,所以需要用端口进行区分。
    这个端口可以自由定义,在本文 mongos为 20000, config server 为 21000, shard1为 22001 , shard2为22002, shard3为22003.
  • 6、在每一台服务器分别启动配置服务器。
                 /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod --configsvr --dbpath /data/mongodbtest/config/data --port 21000 --logpath /data/mongodbtest/config/log/config.log --fork
    
  • 7、在每一台服务器分别启动mongos服务器。
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongos  --configdb 192.168.0.136:21000,192.168.0.137:21000,192.168.0.138:21000  --port 20000   --logpath  /data/mongodbtest/mongos/log/mongos.log --fork
    
  • 8、配置各个分片的副本集。
    #在每个机器里分别设置分片1服务器及副本集shard1
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod --shardsvr --replSet shard1 --port 22001 --dbpath /data/mongodbtest/shard1/data  --logpath /data/mongodbtest/shard1/log/shard1.log --fork --nojournal  --oplogSize 10
    

    为了快速启动并节约测试环境存储空间,这里加上 nojournal 是为了关闭日志信息,在我们的测试环境不需要初始化这么大的redo日志。同样设置 oplogsize是为了降低 local 文件的大小,oplog是一个固定长度的 capped collection,它存在于”local”数据库中,用于记录Replica Sets操作日志。注意,这里的设置是为了测试!

    #在每个机器里分别设置分片2服务器及副本集shard2
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod --shardsvr --replSet shard2 --port 22002 --dbpath /data/mongodbtest/shard2/data  --logpath /data/mongodbtest/shard2/log/shard2.log --fork --nojournal  --oplogSize 10
    
     
    #在每个机器里分别设置分片3服务器及副本集shard3 
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod --shardsvr --replSet shard3 --port 22003 --dbpath /data/mongodbtest/shard3/data  --logpath /data/mongodbtest/shard3/log/shard3.log --fork --nojournal  --oplogSize 10
    

    分别对每个分片配置副本集,深入了解副本集参考本系列前几篇文章。

    任意登陆一个机器,比如登陆192.168.0.136,连接mongodb

               
    #设置第一个分片副本集
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo  127.0.0.1:22001
    
                        
    #使用admin数据库
    use admin
    
    #定义副本集配置
    config = { _id:"shard1", members:[
                         {_id:0,host:"192.168.0.136:22001"},
                         {_id:1,host:"192.168.0.137:22001"},
                         {_id:2,host:"192.168.0.138:22001",arbiterOnly:true}
                    ]
             }
    
    #初始化副本集配置
    rs.initiate(config);
    
     
    #设置第二个分片副本集
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo  127.0.0.1:22002
    
                         
    #使用admin数据库
    use admin
    
     
    #定义副本集配置
    config = { _id:"shard2", members:[
                         {_id:0,host:"192.168.0.136:22002"},
                         {_id:1,host:"192.168.0.137:22002"},
                         {_id:2,host:"192.168.0.138:22002",arbiterOnly:true}
                    ]
             }
    
     
    #初始化副本集配置
    rs.initiate(config);
    
    #设置第三个分片副本集
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo    127.0.0.1:22003
    
                         
    #使用admin数据库
    use admin
    
     
    #定义副本集配置
    config = { _id:"shard3", members:[
                         {_id:0,host:"192.168.0.136:22003"},
                         {_id:1,host:"192.168.0.137:22003"},
                         {_id:2,host:"192.168.0.138:22003",arbiterOnly:true}
                    ]
             }
    
    #初始化副本集配置
    rs.initiate(config);
    
  • 9、目前搭建了mongodb配置服务器、路由服务器,各个分片服务器,不过应用程序连接到 mongos 路由服务器并不能使用分片机制,还需要在程序里设置分片配置,让分片生效。
    #连接到mongos
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo  127.0.0.1:20000
    
    #使用admin数据库
    user  admin
    
     
    #串联路由服务器与分配副本集1
    db.runCommand( { addshard : "shard1/192.168.0.136:22001,192.168.0.137:22001,192.168.0.138:22001"});
    

    如里shard是单台服务器,用 db.runCommand( { addshard : “[: ]” } )这样的命令加入,如果shard是副本集,用db.runCommand( { addshard : “replicaSetName/[:port][,serverhostname2[:port],…]” });这样的格式表示 。

    #串联路由服务器与分配副本集2
    db.runCommand( { addshard : "shard2/192.168.0.136:22002,192.168.0.137:22002,192.168.0.138:22002"});
    
    #串联路由服务器与分配副本集3
    db.runCommand( { addshard : "shard3/192.168.0.136:22003,192.168.0.137:22003,192.168.0.138:22003"});
    
    #查看分片服务器的配置
    db.runCommand( { listshards : 1 } );
    

    #内容输出

            {
                     "shards" : [
                            {
                                    "_id" : "shard1",
                                    "host" : "shard1/192.168.0.136:22001,192.168.0.137:22001"
                            },
                            {
                                    "_id" : "shard2",
                                    "host" : "shard2/192.168.0.136:22002,192.168.0.137:22002"
                            },
                            {
                                    "_id" : "shard3",
                                    "host" : "shard3/192.168.0.136:22003,192.168.0.137:22003"
                            }
                    ],
                    "ok" : 1
            }

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

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

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

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

    我们设置testdb的 table1 表需要分片,根据 id 自动分片到 shard1 ,shard2,shard3 上面去。要这样设置是因为不是所有mongodb 的数据库和表 都需要分片!

  • 11、测试分片配置结果。
    #连接mongos服务器
    /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo  127.0.0.1:20000
    #使用testdb
    use  testdb;
     
    #插入测试数据
    for (var i = 1; i <= 100000; i++) 
    db.table1.save({id:i,"test1":"testval1"});
     
    #查看分片情况如下,部分无关信息省掉了
    db.table1.stats();
                  {
                          "sharded" : true,
                          "ns" : "testdb.table1",
                          "count" : 100000,
                          "numExtents" : 13,
                          "size" : 5600000,
                          "storageSize" : 22372352,
                          "totalIndexSize" : 6213760,
                          "indexSizes" : {
                                  "_id_" : 3335808,
                                  "id_1" : 2877952
                          },
                          "avgObjSize" : 56,
                          "nindexes" : 2,
                          "nchunks" : 3,
                          "shards" : {
                                  "shard1" : {
                                          "ns" : "testdb.table1",
                                          "count" : 42183,
                                          "size" : 0,
                                          ...
                                          "ok" : 1
                                  },
                                  "shard2" : {
                                          "ns" : "testdb.table1",
                                          "count" : 38937,
                                          "size" : 2180472,
                                          ...
                                          "ok" : 1
                                  },
                                  "shard3" : {
                                          "ns" : "testdb.table1",
                                          "count" :18880,
                                          "size" : 3419528,
                                          ...
                                          "ok" : 1
                                  }
                          },
                          "ok" : 1
                  }
    

    可以看到数据分到3个分片,各自分片数量为: shard1 “count” : 42183,shard2 “count” : 38937,shard3 “count” : 18880。已经成功了!不过分的好像不是很均匀,所以这个分片还是很有讲究的,后续再深入讨论。

  • 12、java程序调用分片集群,因为我们配置了三个mongos作为入口,就算其中哪个入口挂掉了都没关系,使用集群客户端程序如下:
    public class TestMongoDBShards {
    
           public static void main(String[] args) {
    
                 try {
                      List<ServerAddress> addresses = new ArrayList<ServerAddress>();
                      ServerAddress address1 = new ServerAddress("192.168.0.136" , 20000);
                      ServerAddress address2 = new ServerAddress("192.168.0.137" , 20000);
                      ServerAddress address3 = new ServerAddress("192.168.0.138" , 20000);
                      addresses.add(address1);
                      addresses.add(address2);
                      addresses.add(address3);
    
                      MongoClient client = new MongoClient(addresses);
                      DB db = client.getDB( "testdb" );
                      DBCollection coll = db.getCollection( "table1" );
    
                      BasicDBObject object = new BasicDBObject();
                      object.append( "id" , 1);
    
                      DBObject dbObject = coll.findOne(object);
    
                      System. out .println(dbObject);
    
                } catch (Exception e) {
                      e.printStackTrace();
                }
          }
    }
    
    

整个分片集群搭建完了,思考一下我们这个架构是不是足够好呢?其实还有很多地方需要优化,比如我们把所有的仲裁节点放在一台机器,其余两台机器承担了全部读写操作,但是作为仲裁的192.168.0.138相当空闲。让机器3 192.168.0.138多分担点责任吧!架构可以这样调整,把机器的负载分的更加均衡一点,每个机器既可以作为主节点、副本节点、仲裁节点,这样压力就会均衡很多了,如图:

fenpian6

当然生产环境的数据远远大于当前的测试数据,大规模数据应用情况下我们不可能把全部的节点像这样部署,硬件瓶颈是硬伤,只能扩展机器。要用好mongodb还有很多机制需要调整,不过通过这个东东我们可以快速实现高可用性、高扩展性,所以它还是一个非常不错的Nosql组件。

再看看我们使用的mongodb java 驱动客户端 MongoClient(addresses),这个可以传入多个mongos 的地址作为mongodb集群的入口,并且可以实现自动故障转移,但是负载均衡做的好不好呢?打开源代码查看:

fenpian7

它的机制是选择一个ping 最快的机器来作为所有请求的入口,如果这台机器挂掉会使用下一台机器。那这样。。。。肯定是不行的!万一出现双十一这样的情况所有请求集中发送到这一台机器,这台机器很有可能挂掉。一但挂掉了,按照它的机制会转移请求到下台机器,但是这个压力总量还是没有减少啊!下一台还是可能崩溃,所以这个架构还有漏洞!不过这个文章已经太长了,后续解决吧。

参考:
http://docs.mongodb.org/manual/core/sharding-introduction/

原创文章,转载请注明: 转载自LANCEYAN.COM

本文链接地址: 搭建高可用mongodb集群(四)—— 分片

在上一篇文章《搭建高可用MongoDB集群(一)——配置MongoDB》 提到了几个问题还没有解决。

  • 主节点挂了能否自动切换连接?目前需要手工切换。
  • 主节点的读写压力过大如何解决?
  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 数据压力大到机器支撑不了的时候能否做到自动扩展?

这篇文章看完这些问题就可以搞定了。NoSQL的产生就是为了解决大数据量、高扩展性、高性能、灵活数据模型、高可用性。但是光通过主从模式的架构远远达不到上面几点,由此MongoDB设计了副本集和分片的功能。这篇文章主要介绍副本集

mongoDB官方已经不建议使用主从模式了,替代方案是采用副本集的模式,点击查看 ,如图:
mongorep1
那什么是副本集呢?打魔兽世界总说打副本,其实这两个概念差不多一个意思。游戏里的副本是指玩家集中在高峰时间去一个场景打怪,会出现玩家暴多怪物少的情况,游戏开发商为了保证玩家的体验度,就为每一批玩家单独开放一个同样的空间同样的数量的怪物,这一个复制的场景就是一个副本,不管有多少个玩家各自在各自的副本里玩不会互相影响。 mongoDB的副本也是这个,主从模式其实就是一个单副本的应用,没有很好的扩展性和容错性。而副本集具有多个副本保证了容错性,就算一个副本挂掉了还有很多副本存在,并且解决了上面第一个问题“主节点挂掉了,整个集群内会自动切换”。难怪mongoDB官方推荐使用这种模式。我们来看看mongoDB副本集的架构图:

mongorep2

由图可以看到客户端连接到整个副本集,不关心具体哪一台机器是否挂掉。主服务器负责整个副本集的读写,副本集定期同步数据备份,一但主节点挂掉,副本节点就会选举一个新的主服务器,这一切对于应用服务器不需要关心。我们看一下主服务器挂掉后的架构:

mongodb故障转移

副本集中的副本节点在主节点挂掉后通过心跳机制检测到后,就会在集群内发起主节点的选举机制,自动选举一位新的主服务器。看起来很牛X的样子,我们赶紧操作部署一下!
官方推荐的副本集机器数量为至少3个,那我们也按照这个数量配置测试。

1、准备两台机器 192.168.1.136、192.168.1.137、192.168.1.138。 192.168.1.136 当作副本集主节点,192.168.1.137、192.168.1.138作为副本集副本节点

2、分别在每台机器上建立mongodb副本集测试文件夹

#存放整个mongodb文件
mkdir -p /data/mongodbtest/replset 

#存放mongodb数据文件
mkdir -p /data/mongodbtest/replset/data

#进入mongodb文件夹
cd  /data/mongodbtest

3、下载mongodb的安装程序包

wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz

注意linux生产环境不能安装32位的mongodb,因为32位受限于操作系统最大2G的文件限制。

mongorep4

#解压下载的压缩包  
tar xvzf mongodb-linux-x86_64-2.4.8.tgz

4、分别在每台机器上启动mongodb

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod  --dbpath /data/mongodbtest/replset/data   --replSet repset 

可以看到控制台上显示副本集还没有配置初始化信息。

Sun Dec 29 20:12:02.953 [rsStart] replSet can't get local.system.replset config from self or any seed (EMPTYCONFIG)
Sun Dec 29 20:12:02.953 [rsStart] replSet info you may need to run  replSetInitiate -- rs.initiate() in the shell -- if that is not already done

5、初始化副本集

在三台机器上任意一台机器登陆mongodb

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo

#使用admin数据库
use admin

#定义副本集配置变量,这里的 _id:”repset” 和上面命令参数“ –replSet repset” 要保持一样。

config = { _id:"repset", members:[
... {_id:0,host:"192.168.1.136:27017"},
... {_id:1,host:"192.168.1.137:27017"},
... {_id:2,host:"192.168.1.138:27017"}]
... }

#输出

{
        "_id" : "repset",
        "members" : [
                {
                        "_id" : 0,
                        "host" : "192.168.1.136:27017"
                },
                {
                        "_id" : 1,
                        "host" : "192.168.1.137:27017"
                },
                {
                        "_id" : 2,
                        "host" : "192.168.1.138:27017"
                }
        ]
}
#初始化副本集配置
rs.initiate(config);

#输出成功

{
        "info" : "Config now saved locally.  Should come online in about a minute.",
        "ok" : 1
}

#查看日志,副本集启动成功后,138为主节点PRIMARY,136、137为副本节点SECONDARY

Sun Dec 29 20:26:13.842 [conn3] replSet replSetInitiate admin command received from client
Sun Dec 29 20:26:13.842 [conn3] replSet replSetInitiate config object parses ok, 3 members specified
Sun Dec 29 20:26:13.847 [conn3] replSet replSetInitiate all members seem up
Sun Dec 29 20:26:13.848 [conn3] ******
Sun Dec 29 20:26:13.848 [conn3] creating replication oplog of size: 990MB...
Sun Dec 29 20:26:13.849 [FileAllocator] allocating new datafile /data/mongodbtest/replset/data/local.1, filling with zeroes...
Sun Dec 29 20:26:13.862 [FileAllocator] done allocating datafile /data/mongodbtest/replset/data/local.1, size: 1024MB,  took 0.012 secs
Sun Dec 29 20:26:13.863 [conn3] ******
Sun Dec 29 20:26:13.863 [conn3] replSet info saving a newer config version to local.system.replset
Sun Dec 29 20:26:13.864 [conn3] replSet saveConfigLocally done
Sun Dec 29 20:26:13.864 [conn3] replSet replSetInitiate config now saved locally.  Should come online in about a minute.
Sun Dec 29 20:26:23.047 [rsStart] replSet I am 192.168.1.138:27017
Sun Dec 29 20:26:23.048 [rsStart] replSet STARTUP2
Sun Dec 29 20:26:23.049 [rsHealthPoll] replSet member 192.168.1.137:27017 is up
Sun Dec 29 20:26:23.049 [rsHealthPoll] replSet member 192.168.1.136:27017 is up
Sun Dec 29 20:26:24.051 [rsSync] replSet SECONDARY
Sun Dec 29 20:26:25.053 [rsHealthPoll] replset info 192.168.1.136:27017 thinks that we are down
Sun Dec 29 20:26:25.053 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state STARTUP2
Sun Dec 29 20:26:25.056 [rsMgr] not electing self, 192.168.1.136:27017 would veto with 'I don't think 192.168.1.138:27017 is electable'
Sun Dec 29 20:26:31.059 [rsHealthPoll] replset info 192.168.1.137:27017 thinks that we are down
Sun Dec 29 20:26:31.059 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state STARTUP2
Sun Dec 29 20:26:31.062 [rsMgr] not electing self, 192.168.1.137:27017 would veto with 'I don't think 192.168.1.138:27017 is electable'
Sun Dec 29 20:26:37.074 [rsMgr] replSet info electSelf 2
Sun Dec 29 20:26:38.062 [rsMgr] replSet PRIMARY
Sun Dec 29 20:26:39.071 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state RECOVERING
Sun Dec 29 20:26:39.075 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state RECOVERING
Sun Dec 29 20:26:42.201 [slaveTracking] build index local.slaves { _id: 1 }
Sun Dec 29 20:26:42.207 [slaveTracking] build index done.  scanned 0 total records. 0.005 secs
Sun Dec 29 20:26:43.079 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state SECONDARY
Sun Dec 29 20:26:49.080 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state SECONDARY
#查看集群节点的状态
 rs.status();

#输出

{
        "set" : "repset",
        "date" : ISODate("2013-12-29T12:54:25Z"),
        "myState" : 1,
        "members" : [
                {
                        "_id" : 0,
                        "name" : "192.168.1.136:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 1682,
                        "optime" : Timestamp(1388319973, 1),
                        "optimeDate" : ISODate("2013-12-29T12:26:13Z"),
                        "lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),
                        "lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),
                        "pingMs" : 1,
                        "syncingTo" : "192.168.1.138:27017"
                },
                {
                        "_id" : 1,
                        "name" : "192.168.1.137:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 1682,
                        "optime" : Timestamp(1388319973, 1),
                        "optimeDate" : ISODate("2013-12-29T12:26:13Z"),
                        "lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),
                        "lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),
                        "pingMs" : 1,
                        "syncingTo" : "192.168.1.138:27017"
                },
                {
                        "_id" : 2,
                        "name" : "192.168.1.138:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 2543,
                        "optime" : Timestamp(1388319973, 1),
                        "optimeDate" : ISODate("2013-12-29T12:26:13Z"),
                        "self" : true
                }
        ],
        "ok" : 1
}

整个副本集已经搭建成功了。

6、测试副本集数据复制功能

#在主节点192.168.1.138 上连接到终端:
mongo 127.0.0.1

#建立test 数据库。
use test;

往testdb表插入数据。
> db.testdb.insert({"test1":"testval1"})

#在副本节点 192.168.1.136、192.168.1.137 上连接到mongodb查看数据是否复制过来。
/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 192.168.1.136:27017

#使用test 数据库。
repset:SECONDARY> use test;

repset:SECONDARY> show tables;

#输出

Sun Dec 29 21:50:48.590 error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:128
          
#mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。
repset:SECONDARY> db.getMongo().setSlaveOk();

#可以看到数据已经复制到了副本集。
repset:SECONDARY> db.testdb.find();
#输出
{ "_id" : ObjectId("52c028460c7505626a93944f"), "test1" : "testval1" }

7、测试副本集故障转移功能

先停掉主节点mongodb 138,查看136、137的日志可以看到经过一系列的投票选择操作,137 当选主节点,136从137同步数据过来。

Sun Dec 29 22:03:05.351 [rsBackgroundSync] replSet sync source problem: 10278 dbclient error communicating with server: 192.168.1.138:27017
Sun Dec 29 22:03:05.354 [rsBackgroundSync] replSet syncing to: 192.168.1.138:27017
Sun Dec 29 22:03:05.356 [rsBackgroundSync] repl: couldn't connect to server 192.168.1.138:27017
Sun Dec 29 22:03:05.356 [rsBackgroundSync] replSet not trying to sync from 192.168.1.138:27017, it is vetoed for 10 more seconds
Sun Dec 29 22:03:05.499 [rsHealthPoll] DBClientCursor::init call() failed
Sun Dec 29 22:03:05.499 [rsHealthPoll] replset info 192.168.1.138:27017 heartbeat failed, retrying
Sun Dec 29 22:03:05.501 [rsHealthPoll] replSet info 192.168.1.138:27017 is down (or slow to respond):
Sun Dec 29 22:03:05.501 [rsHealthPoll] replSet member 192.168.1.138:27017 is now in state DOWN
Sun Dec 29 22:03:05.511 [rsMgr] not electing self, 192.168.1.137:27017 would veto with '192.168.1.136:27017 is trying to elect itself but 192.168.1.138:27017 is already primary and more up-to-date'
Sun Dec 29 22:03:07.330 [conn393] replSet info voting yea for 192.168.1.137:27017 (1)
Sun Dec 29 22:03:07.503 [rsHealthPoll] replset info 192.168.1.138:27017 heartbeat failed, retrying
Sun Dec 29 22:03:08.462 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state PRIMARY
Sun Dec 29 22:03:09.359 [rsBackgroundSync] replSet syncing to: 192.168.1.137:27017
Sun Dec 29 22:03:09.507 [rsHealthPoll] replset info 192.168.1.138:27017 heartbeat failed, retrying

查看整个集群的状态,可以看到138为状态不可达。

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 192.168.1.136:27017

repset:SECONDARY> rs.status();

#输出

{
        "set" : "repset",
        "date" : ISODate("2013-12-29T14:28:35Z"),
        "myState" : 2,
        "syncingTo" : "192.168.1.137:27017",
        "members" : [
                {
                        "_id" : 0,
                        "name" : "192.168.1.136:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 9072,
                        "optime" : Timestamp(1388324934, 1),
                        "optimeDate" : ISODate("2013-12-29T13:48:54Z"),
                        "self" : true
                },
                {
                        "_id" : 1,
                        "name" : "192.168.1.137:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 7329,
                        "optime" : Timestamp(1388324934, 1),
                        "optimeDate" : ISODate("2013-12-29T13:48:54Z"),
                        "lastHeartbeat" : ISODate("2013-12-29T14:28:34Z"),
                        "lastHeartbeatRecv" : ISODate("2013-12-29T14:28:34Z"),
                        "pingMs" : 1,
                        "syncingTo" : "192.168.1.138:27017"
                },
                {
                        "_id" : 2,
                        "name" : "192.168.1.138:27017",
                        "health" : 0,
                        "state" : 8,
                        "stateStr" : "(not reachable/healthy)",
                        "uptime" : 0,
                        "optime" : Timestamp(1388324934, 1),
                        "optimeDate" : ISODate("2013-12-29T13:48:54Z"),
                        "lastHeartbeat" : ISODate("2013-12-29T14:28:35Z"),
                        "lastHeartbeatRecv" : ISODate("2013-12-29T14:28:23Z"),
                        "pingMs" : 0,
                        "syncingTo" : "192.168.1.137:27017"
                }
        ],
        "ok" : 1
}

再启动原来的主节点 138,发现138 变为 SECONDARY,还是137 为主节点 PRIMARY。

Sun Dec 29 22:21:06.619 [rsStart] replSet I am 192.168.1.138:27017
Sun Dec 29 22:21:06.619 [rsStart] replSet STARTUP2
Sun Dec 29 22:21:06.627 [rsHealthPoll] replset info 192.168.1.136:27017 thinks that we are down
Sun Dec 29 22:21:06.627 [rsHealthPoll] replSet member 192.168.1.136:27017 is up
Sun Dec 29 22:21:06.627 [rsHealthPoll] replSet member 192.168.1.136:27017 is now in state SECONDARY
Sun Dec 29 22:21:07.628 [rsSync] replSet SECONDARY
Sun Dec 29 22:21:08.623 [rsHealthPoll] replSet member 192.168.1.137:27017 is up
Sun Dec 29 22:21:08.624 [rsHealthPoll] replSet member 192.168.1.137:27017 is now in state PRIMARY

8、java程序连接副本集测试。三个节点有一个节点挂掉也不会影响应用程序客户端对整个副本集的读写!

public class TestMongoDBReplSet {

        public static void main(String[] args) {

               try {
                     List<ServerAddress> addresses = new ArrayList<ServerAddress>();
                     ServerAddress address1 = new ServerAddress("192.168.1.136" , 27017);
                     ServerAddress address2 = new ServerAddress("192.168.1.137" , 27017);
                     ServerAddress address3 = new ServerAddress("192.168.1.138" , 27017);
                     addresses.add(address1);
                     addresses.add(address2);
                     addresses.add(address3);

                     MongoClient client = new MongoClient(addresses);
                     DB db = client.getDB( "test");
                     DBCollection coll = db.getCollection( "testdb");

                      // 插入
                     BasicDBObject object = new BasicDBObject();
                     object.append( "test2", "testval2" );

                     coll.insert(object);

                     DBCursor dbCursor = coll.find();

                      while (dbCursor.hasNext()) {
                           DBObject dbObject = dbCursor.next();
                           System. out.println(dbObject.toString());
                     }

              } catch (Exception e) {
                     e.printStackTrace();
              }

       }

}

目前看起来支持完美的故障转移了,这个架构是不是比较完美了?其实还有很多地方可以优化,比如开头的第二个问题:主节点的读写压力过大如何解决?常见的解决方案是读写分离,mongodb副本集的读写分离如何做呢?

看图说话:

mongorep5

常规写操作来说并没有读操作多,所以一台主节点负责写,两台副本节点负责读。

1、设置读写分离需要先在副本节点SECONDARY 设置 setSlaveOk。
2、在程序中设置副本节点负责读操作,如下代码:

public class TestMongoDBReplSetReadSplit {

        public static void main(String[] args) {

               try {
                     List<ServerAddress> addresses = new ArrayList<ServerAddress>();
                     ServerAddress address1 = new ServerAddress("192.168.1.136" , 27017);
                     ServerAddress address2 = new ServerAddress("192.168.1.137" , 27017);
                     ServerAddress address3 = new ServerAddress("192.168.1.138" , 27017);
                     addresses.add(address1);
                     addresses.add(address2);
                     addresses.add(address3);

                     MongoClient client = new MongoClient(addresses);
                     DB db = client.getDB( "test" );
                     DBCollection coll = db.getCollection( "testdb" );


                     BasicDBObject object = new BasicDBObject();
                     object.append( "test2" , "testval2" );

                      //读操作从副本节点读取
                     ReadPreference preference = ReadPreference. secondary();
                     DBObject dbObject = coll.findOne(object, null , preference);

                     System. out .println(dbObject);


              } catch (Exception e) {
                     e.printStackTrace();
              }
       }
}

读参数除了secondary一共还有五个参数:primary、primaryPreferred、secondary、secondaryPreferred、nearest。

mongorep6

primary:默认参数,只从主节点上进行读取操作;
primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。
secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。
secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据;
nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。

好,读写分离做好我们可以数据分流,减轻压力解决了“主节点的读写压力过大如何解决?”这个问题。不过当我们的副本节点增多时,主节点的复制压力会加大有什么办法解决吗?mongodb早就有了相应的解决方案。

看图:
mongorep7

其中的仲裁节点不存储数据,只是负责故障转移的群体投票,这样就少了数据复制的压力。是不是想得很周到啊,一看mongodb的开发兄弟熟知大数据架构体系,其实不只是主节点、副本节点、仲裁节点,还有Secondary-Only、Hidden、Delayed、Non-Voting。

Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止一些性能不高的节点成为主节点。
Hidden:这类节点是不能够被客户端制定IP引用,也不能被设置为主节点,但是可以投票,一般用于备份数据。
Delayed:可以指定一个时间延迟从primary节点同步数据。主要用于备份数据,如果实时同步,误删除数据马上同步到从节点,恢复又恢复不了。
Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。

到此整个mongodb副本集搞定了两个问题:

  • 主节点挂了能否自动切换连接?目前需要手工切换。
  • 主节点的读写压力过大如何解决?

还有这两个问题后续解决:

  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 数据压力大到机器支撑不了的时候能否做到自动扩展?

做了副本集发现又一些问题:

  • 副本集故障转移,主节点是如何选举的?能否手动干涉下架某一台主节点。
  • 官方说副本集数量最好是奇数,为什么?
  • mongodb副本集是如何同步的?如果同步不及时会出现什么情况?会不会出现不一致性?
  • mongodb的故障转移会不会无故自动发生?什么条件会触发?频繁触发可能会带来系统负载加重

参考:

http://cn.docs.mongodb.org/manual/administration/replica-set-member-configuration/

http://docs.mongodb.org/manual/reference/connection-string/

http://www.cnblogs.com/magialmoon/p/3268963.html

原创文章,转载请注明: 转载自LANCEYAN.COM

本文链接地址: 搭建高可用mongodb集群(二)—— 副本集

在大数据的时代,传统的关系型数据库要能更高的服务必须要解决高并发读写、海量数据高效存储、高可扩展性和高可用性这些难题。不过就是因为这些问题Nosql诞生了。

NOSQL有这些优势:

大数据量,可以通过廉价服务器存储大量的数据,轻松摆脱传统mysql单表存储量级限制。

高扩展性,Nosql去掉了关系数据库的关系型特性,很容易横向扩展,摆脱了以往老是纵向扩展的诟病。

高性能,Nosql通过简单的key-value方式获取数据,非常快速。还有NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说就要性能高很多。

灵活的数据模型,NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。

高可用,NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如mongodb通过mongos、mongo分片就可以快速配置出高可用配置。

在nosql数据库里,大部分的查询都是键值对(key、value)的方式。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中最像关系数据库的。支持类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。所以这个非常方便,我们可以用sql操作MongoDB,从关系型数据库迁移过来,开发人员学习成本会大大减少。如果再对底层的sql API做一层封装,开发基本可以感觉不到mongodb和关系型数据库的区别。同样MongoDB也是号称自己能够快速搭建一个高可用可扩展的的分布式集群,网上有很多搭建的文章,在我们搭建的时候还需要查找修改很多东西,所以把自己实战的步骤记录下来以备忘。我们看看如何一步一步搭建这个东东。

一、mongodb单实例。这种配置只适合简易开发时使用,生产使用不行,因为单节点挂掉整个数据业务全挂,如下图。

mongodb1

虽然不能生产使用,但这个模式可以快速搭建启动,并且能够用mongodb的命令操作数据库。下面列出在linux下安装单节点mongodb的步骤

1、建立mongodb测试文件夹

#存放整个mongodb文件
mkdir -p /data/mongodbtest/single

#存放mongodb数据文件
mkdir -p /data/mongodbtest/single/data

#进入mongodb文件夹
cd  /data/mongodbtest/single

2、下载mongodb的安装程序包

wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.6.tgz

#解压下载的压缩包  
tar xvzf mongodb-linux-x86_64-2.4.6.tgz

#进入mongodb程序执行文件夹
cd mongodb-linux-x86_64-2.4.6/bin/

3、启动单实例mongodb

mongod  --dbpath /data/mongodbtest/single/data

输出日志如下,成功!

[initandlisten] db version v2.4.6
……..
[initandlisten] waiting for connections on port 27017
[websvr] admin web console waiting for connections on port 28017

mongodb默认自带提供了web访问接口,通过 IP + 端口的形式可以访问。

http://192.168.0.1:28017/

mongodb2

二、主从模式。使用mysql数据库时大家广泛用到,采用双机备份后主节点挂掉了后从节点可以接替主机继续服务。所以这种模式比单节点的高可用性要好很多。

mongodb3

下面看一下怎么一步步搭建一个mongodb的主从复制节点:

  • 1、准备两台机器 192.168.0.1 和 192.168.0.2。 192.168.0.1 当作主节点, 192.168.0.2作为从节点
  • 2、分别下载mongodb安装程序包。在192.168.0.1上建立文件夹 /data/mongodbtest/master,192.168.0.2建立文件夹/data/mongodbtest/slave
  • 3、在192.168.0.1启动mongodb主节点程序。注意后面的这个 “ –master ”参数,标示主节点。

    mongod –dbpath /data/mongodbtest/master –master

输出日志如下,成功!

[initandlisten] MongoDB starting : pid=18285 port=27017 dbpath=/data/mongodbtest/master master=1
#日志显示主节点参数
[initandlisten] options: { dbpath: “/data/mongodbtest/master”, master: true }
……..
[initandlisten] waiting for connections on port 27017

4、在192.168.0.2启动mongodb从节点程序。关键配置,指定主节点ip地址和端口 –source 192.168.0.1:27017 和 标示从节点 –source 参数。

mongod –dbpath /data/mongodbtest/slave –slave –source 192.168.0.1:27017

输出日志如下,成功!

[initandlisten] MongoDB starting : pid=17888 port=27017 dbpath=/data/mongodbtest/slave slave=1
……..
#日志显示从节点参数
[initandlisten] options: { dbpath: “/data/mongodbtest/slave”, slave: true, source: “192.168.0.1:27017″ }
……..
[initandlisten] waiting for connections on port 27017
#日志显示从节点 从主节点同步复制数据
[replslave] repl: from host:192.168.0.1:27017

5、测试主从复制。

在主节点上连接到终端:

mongo 127.0.0.1

#建立test 数据库。
use test;

往testdb表插入数据。
> db.testdb.insert({"test1":"testval1"})

查询testdb数据看看是否成功。
> db.testdb.find();
{ "_id" : ObjectId("5284e5cb1f4eb215b2ecc463"), "test1" : "testval1" }

可以看到主机的同步日志

[initandlisten] connection accepted from 192.168.0.2:37285 #3 (2 connections now open)
[slaveTracking] update local.slaves query: { _id: ObjectId(’5284e6268ed115d6238bdb39′), config: { host: “192.168.0.2:35271″, upgradeNeeded: true }, ns: “local.oplog.$main” } update: { $set: { syncedTo: Timestamp 1384441570000|1 } } nscanned:1 nupdated:1 fastmod:1 keyUpdates:0 locks(micros) w:132015 132ms

检查从主机的数据。

mongo 127.0.0.1

查看当前数据库。

> show dbs;
  local   0.203125GB
  test    0.203125GB

use test;
db.testdb.find();
{ "_id" : ObjectId("5284e5cb1f4eb215b2ecc463"), "test1" : "testval1" }

查询后数据已经同步过来了。再看看日志,发现从主机确实从主机同步了数据。

Thu Nov 14 23:05:13 [replslave] repl:   checkpoint applied 15 operations
Thu Nov 14 23:05:13 [replslave] repl:   syncedTo: Nov 14 23:08:10 5284e75a:1

查看服务状态

> db.printReplicationInfo();
          this is a slave, printing slave replication info.
          source:   192.168.0.1:27017
              syncedTo: Sun Nov 17 2013 16:04:02 GMT+0800 (CST)
                      = -54 secs ago (-0.01hrs)

到此主从结构的mongodb搭建好了。

故障转移测试,现在两台服务器如果主服务器挂掉了,从服务器可以正常运转吗?

  • a、先测试下从服务器可以当成主服务器吗,也就是往从服务器里写能够同步主服务器吗?

    在192.168.0.2上连接mongodb。

    mongo 127.0.0.1:27017
    > db.testdb.insert({"test3":"testval3"});
    not master

    可以看到 mongodb的从节点是不能提供写操作的,只能提供读操作。

  • b、如果从服务器挂掉,主服务器还可以提供服务。如果主服务器挂掉了从服务器能否自动变为可写。
    测试一下!

    先杀掉原来的mongodb主服务器。

    kill -3 `ps -ef|grep mongod|grep -v grep|awk '{print $2}'`

    测试从服务器能否可写。在192.168.0.2上连接mongodb测试。

    > db.testdb.insert({"test3":"testval3"});
    not master

    看起来从服务器没有自动接替主服务器的功能,只有手工处理了!

    停止从服务器,在原数据文件启动并添加主服务器标示。

    mongod  --dbpath /data/mongodbtest/slave --master

    等到启动成功(时间有点长)。在192.168.0.2 上 连接

    mongo 192.168.0.2:27017

    > db.testdb.find();
    { "_id" : ObjectId("5288629e9b0318be4b20bd4c"), "test1" : "testval1" }
    { "_id" : ObjectId("528862d69b0318be4b20bd4d"), "test2" : "testval2" }

    成功!

多个从节点。现在只是一个数据库服务器又提供写又提供读,机器承载会出现瓶颈。大家还记得mysql里的读写分离吗?把20%的写放到主节点,80%的读放到从节点分摊了减少了服务器的负载。但是大部分应用都是读操作带来的压力,一个从节点压力负载不了,可以把一个从节点变成多个节点。那mongodb的一主多从可以支持吗?答案是肯定的。

mongodb4

为了方便测试,在192.168.0.2上再建立一个文件夹 /data/mongodbtest/slave1 作为另一个slave服务器。
启动slave2服务,

mongod  --dbpath /data/mongodbtest/slave1 --slave  --port 27017 --source 192.168.0.1:27017

成功启动后通过mongodb连接测试:

> db.testdb.find();
{ "_id" : ObjectId("5288629e9b0318be4b20bd4c"), "test1" : "testval1" }
{ "_id" : ObjectId("528862d69b0318be4b20bd4d"), "test2" : "testval2" }

搭建了这套主从复制系统是不是就很稳健了,其实不然。。。看看这几个问题?

  • 主节点挂了能否自动切换连接?目前需要手工切换。
  • 主节点的写压力过大如何解决?
  • 从节点每个上面的数据都是对数据库全量拷贝,从节点压力会不会过大?
  • 就算对从节点路由实施路由访问策略能否做到自动扩展?

还有这么多问题,有其他解决方案吗?下一篇接着弄。

参考:
NoSQL开篇——为什么要使用NoSQL http://www.infoq.com/cn/news/2011/01/nosql-why/
mongodb手册 http://cn.docs.mongodb.org/manual/single/

原创文章,转载请注明: 转载自LANCEYAN.COM

本文链接地址: 搭建高可用mongodb集群(一)——配置mongodb