那什么是副本集呢?打魔兽世界总说打副本,其实这两个概念差不多一个意思。游戏里的副本是指玩家集中在高峰时间去一个场景打怪,会出现玩家暴多怪物少的情况,游戏开发商为了保证玩家的体验度,就为每一批玩家单独开放一个同样的空间同样的数量的怪物,这一个复制的场景就是一个副本,不管有多少个玩家各自在各自的副本里玩不会互相影响。 mongoDB的副本也是这个,主从模式其实就是一个单副本的应用,没有很好的扩展性和容错性。而副本集具有多个副本保证了容错性,就算一个副本挂掉了还有很多副本存在,并且解决了上面第一个问题“主节点挂掉了,整个集群内会自动切换”。
在MongoDB副本集中,主节点负责处理客户端的读写请求,备份节点则负责映射主节点的数据。
备份节点的工作原理过程可以大致描述为,备份节点定期轮询主节点上的数据操作,然后对自己的数据副本进行这些操作,从而保证跟主节点的数据同步。
至于主节点上的所有 数据库状态改变 的操作,都会存放在一张特定的系统表中。备份节点则是根据这些数据进行自己的数据更新。
oplog
上面提到的 数据库状态改变 的操作,称为oplog(operation log,主节点操作记录)。oplog存储在local数据库的"oplog.rs"表中。副本集中备份节点异步的从主节点同步oplog,然后重新执行它记录的操作,以此达到了数据同步的作用。
关于oplog有几个注意的地方:
oplog只记录改变数据库状态的操作
存储在oplog中的操作并不是和主节点执行的操作完全一样,例如"$inc"操作就会转化为"$set"操作
oplog存储在固定集合中(capped collection),当oplog的数量超过oplogSize,新的操作就会覆盖就的操作
实践操作:
副本集搭建最少3台机器,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 程序包不需要安装编译,下载后解压后可以直接使用
tar xvf mongodb-linux-x86_64-2.6.4.tgz
mv mongodb-linux-x86_64-2.6.4 /usr/local/mongodb/
mkdir -p /usr/local/mongodb/data/
mkdir -p /usr/local/mongodb/logs/
3,配置mongodb 配置文件
vi /usr/local/mongodb/bin/mongodb.conf
bind_ip=192.168.1.136 # 这个根据服务ip填写
dbpath=/usr/local/mongodb/data
logpath=/usr/local/mongodb/logs/mongodb.log
port=27017
directoryperdb=true
maxConns=100000
storageEngine= wiredTigerfork=true
replSet= repset2
fork=true
4,在每台服务器启动mongodb
/usr/local/bin/mongod -f mongodb.conf & # 后台运行
5,把mongodb 命令变为全局环境变量
vi /etc/profile
在文件最后面添加
export PATH=$PATH:/usr/local/mongodb/bin/
然后保存退出
source /etc/profile
6,初始化副本集
在三台机器上任意一台机器登陆mongodb,
#使用admin数据库
use admin
config = { _id:"repset2", 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" : "repset2",
"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
}
#查看集群节点的状态 rs.status();
输出
{
"set" : "repset2",
"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
}
7#在主节点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 数据库。
repset2:SECONDARY> use test;
repset2: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默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。
repset2:SECONDARY> db.getMongo().setSlaveOk();
#可以看到数据已经复制到了副本集。
repset2:SECONDARY> db.testdb.find();
8、测试副本集故障转移功能
先停掉主节点mongodb 138,查看136、137的日志可以看到经过一系列的投票选择操作,137 当选主节点,136从137同步数据过来。
#rs.status()
9、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();
} } }