一、概念:
分片(sharding)是指将数据库拆分,将其分散在不同的机器上的过程。将数据分散到不同的机器上,不需要功能强大的服务器就可以存储更多的数据和处理更大的负载。基本思想就是将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分,最后通过一个均衡器来对各个分片进行均衡(数据迁移)。通过一个名为mongos的路由进程进行操作,mongos知道数据和片的对应关系(通过配置服务器)。大部分使用场景都是解决磁盘空间的问题,对于写入有可能会变差(+++里面的说明+++),查询则尽量避免跨分片查询。使用分片的时机:
,机器的磁盘不够用了。使用分片解决磁盘空间的问题。
,单个mongod已经不能满足写数据的性能要求。通过分片让写压力分散到各个分片上面,使用分片服务器自身的资源。
,想把大量数据放到内存里提高性能。和上面一样,通过分片使用分片服务器自身的资源。
二、部署安装: 前提是安装了mongodb(本文用3.0测试)
在搭建分片之前,先了解下分片中各个角色的作用。
① 配置服务器。是一个独立的mongod进程,保存集群和分片的元数据,即各分片包含了哪些数据的信息。最先开始建立,启用日志功能。像启动普通的mongod一样启动配置服务器,指定configsvr选项。不需要太多的空间和资源,配置服务器的1KB空间相当于真是数据的200MB。保存的只是数据的分布表。当服务不可用,则变成只读,无法分块、迁移数据。
② 路由服务器。即mongos,起到一个路由的功能,供程序连接。本身不保存数据,在启动时从配置服务器加载集群信息,开启mongos进程需要知道配置服务器的地址,指定configdb选项。
③ 分片服务器。是一个独立普通的mongod进程,保存数据信息。可以是一个副本集也可以是单独的一台服务器。
部署环境:3台机子
A:配置(3)、路由1、分片1;
B:分片2,路由2;
C:分片3
在部署之前先明白片键的意义,一个好的片键对分片至关重要。片键必须是一个索引,数据根据这个片键进行拆分分散。通过sh.shardCollection加会自动创建索引。一个自增的片键对写入和数据均匀分布就不是很好,因为自增的片键总会在一个分片上写入,后续达到某个阀值可能会写到别的分片。但是按照片键查询会非常高效。随机片键对数据的均匀分布效果很好。注意尽量避免在多个分片上进行查询。在所有分片上查询,mongos会对结果进行归并排序。
启动上面这些服务,因为在后台运行,所以用配置文件启动,配置文件说明。
1)配置服务器的启动。(A上开启3个,Port:20000、21000、22000)
配置服务器是一个普通的mongod进程,所以只需要新开一个实例即可。配置服务器必须开启1个或则3个,开启2个则会报错:
BadValue need either or configdbs
因为要放到后台用用配置文件启动,需要修改配置文件:
/etc/mongod_20000.conf
#数据目录
dbpath=/usr/local/config/
#日志文件
logpath=/var/log/mongodb/mongodb_config.log
#日志追加
logappend=true
#端口
port =
#最大连接数
maxConns =
pidfilepath = /var/run/mongo_20000.pid
#日志,redo log
journal = true
#刷写提交机制
journalCommitInterval =
#守护进程模式
fork = true
#刷写数据到日志的频率
syncdelay =
#storageEngine = wiredTiger
#操作日志,单位M
oplogSize =
#命名空间的文件大小,默认16M,最大2G。
nssize =
noauth = true
unixSocketPrefix = /tmp
configsvr = true
/etc/mongod_21000.conf
数据目录
dbpath=/usr/local/config1/
#日志文件
logpath=/var/log/mongodb/mongodb_config1.log
#日志追加
logappend=true
#端口
port =
#最大连接数
maxConns =
pidfilepath = /var/run/mongo_21000.pid
#日志,redo log
journal = true
#刷写提交机制
journalCommitInterval =
#守护进程模式
fork = true
#刷写数据到日志的频率
syncdelay =
#storageEngine = wiredTiger
#操作日志,单位M
oplogSize =
#命名空间的文件大小,默认16M,最大2G。
nssize =
noauth = true
unixSocketPrefix = /tmp
configsvr = true
开启配置服务器:
root@mongo1:~# mongod -f /etc/mongod_20000.conf
about to fork child process, waiting until server is ready for connections.
forked process:
child process started successfully, parent exiting root@mongo1:~# mongod -f /etc/mongod_21000.conf
about to fork child process, waiting until server is ready for connections.
forked process:
child process started successfully, parent exiting
同理再起一个22000端口的配置服务器。
#数据目录
dbpath=/usr/local/config2/
#日志文件
logpath=/var/log/mongodb/mongodb_config2.log
#日志追加
logappend=true
#端口
port =
#最大连接数
maxConns =
pidfilepath = /var/run/mongo_22000.pid
#日志,redo log
journal = true
#刷写提交机制
journalCommitInterval =
#守护进程模式
fork = true
#刷写数据到日志的频率
syncdelay =
#storageEngine = wiredTiger
#操作日志,单位M
oplogSize =
#命名空间的文件大小,默认16M,最大2G。
nssize = noauth = true
unixSocketPrefix = /tmp configsvr = true
2)路由服务器的启动。(A、B上各开启1个,Port:30000)
路由服务器不保存数据,把日志记录一下即可。
# mongos #日志文件
logpath=/var/log/mongodb/mongodb_route.log
#日志追加
logappend=true
#端口
port =
#最大连接数
maxConns =
#绑定地址
#bind_ip=192.168..*,..., pidfilepath = /var/run/mongo_30000.pid configdb=192.168.200.A:,192.168.200.A:21000,192.168.200.A:22000 #必须是1个或则3个配置 。
#configdb=127.0.0.1:20000 #报错
#守护进程模式 fork = true
其中最重要的参数是configdb,不能在其后面带的配置服务器的地址写成localhost或则127.0.0.1,需要设置成其他分片也能访问的地址,即192.168.200.A:20000/21000/22000。否则在addshard的时候会报错:
{
"ok" : ,
"errmsg" : "can't use localhost as a shard since all shards need to communicate. either use all shards and configdbs in localhost or all in actual IPs host: 172.16.5.104:20000 isLocalHost:0"
}
开启mongos:
root@mongo1:~# mongos -f /etc/mongod_30000.conf
--10T14::58.741+ W SHARDING running with config server should be done only for testing purposes and is not recommended for production
about to fork child process, waiting until server is ready for connections.
forked process:
child process started successfully, parent exiting
3)分片服务器的启动:
就是一个普通的mongod进程:
root@mongo1:~# mongod -f /etc/mongod_40000.conf
note: noprealloc may hurt performance in many applications
about to fork child process, waiting until server is ready for connections.
forked process:
child process started successfully, parent exiting
A服务器上面的服务开启完毕
root@mongo1:~# ps -ef | grep mongo
root : ? :: mongod -f /etc/mongod_40000.conf
root : ? :: mongod -f /etc/mongod_20000.conf
root : ? :: mongod -f /etc/mongod_21000.conf
root : ? :: mongod -f /etc/mongod_22000.conf
root : ? :: mongos -f /etc/mongod_30000.conf
按照上面的方法再到B上开启分片服务和路由服务(配置文件一样),以及在C上开启分片服务。到此分片的配置服务器、路由服务器、分片服务器都已经部署完成。
三、配置分片:下面的操作都是在mongodb的命令行里执行
1)添加分片:sh.addShard("IP:Port")
登陆路由服务器mongos 操作:
root@mongo1:~# mongo --port=
MongoDB shell version: 3.0.
connecting to: 127.0.0.1:/test
mongos>
添加分片:
mongos> sh.status() #查看集群的信息
--- Sharding Status ---
sharding version: {
"_id" : ,
"minCompatibleVersion" : ,
"currentVersion" : ,
"clusterId" : ObjectId("559f72470f93270ba60b26c6")
}
shards:
balancer:
Currently enabled: yes
Currently running: no
Failed balancer rounds in last attempts:
Migration Results for the last hours:
No recent migrations
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" } mongos> sh.addShard("192.168.200.A:40000") #添加分片
{ "shardAdded" : "shard0000", "ok" : }
mongos> sh.addShard("192.168.200.B:40000") #添加分片
{ "shardAdded" : "shard0001", "ok" : }
mongos> sh.addShard("192.168.200.C:40000") #添加分片
{ "shardAdded" : "shard0002", "ok" : } mongos> sh.status() #查看集群信息
--- Sharding Status ---
sharding version: {
"_id" : ,
"minCompatibleVersion" : ,
"currentVersion" : ,
"clusterId" : ObjectId("559f72470f93270ba60b26c6")
}
shards: #分片信息
{ "_id" : "shard0000", "host" : "192.168.200.A:40000" }
{ "_id" : "shard0001", "host" : "192.168.200.B:40000" }
{ "_id" : "shard0002", "host" : "192.168.200.C:40000" }
balancer:
Currently enabled: yes
Currently running: no
Failed balancer rounds in last attempts:
Migration Results for the last hours:
No recent migrations
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
2)开启分片功能:sh.enableSharding("库名")、sh.shardCollection("库名.集合名",{"key":1})
mongos> sh.enableSharding("dba") #首先对数据库启用分片
{ "ok" : }
mongos> sh.status() #查看分片信息
--- Sharding Status ---...
...
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } mongos> sh.shardCollection("dba.account",{"name":}) #再对集合进行分片,name字段是片键。片键的选择:利于分块、分散写请求、查询数据。
{ "collectionsharded" : "dba.account", "ok" : }
mongos> sh.status()
--- Sharding Status ---...
shards:
{ "_id" : "shard0000", "host" : "192.168.200.51:40000" }
{ "_id" : "shard0001", "host" : "192.168.200.52:40000" }
{ "_id" : "shard0002", "host" : "192.168.200.53:40000" }
...
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } #库
dba.account
shard key: { "name" : } #集合
chunks:
shard0000
{ "name" : { "$minKey" : } } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, )
上面加粗部分表示分片信息已经配置完成。要是出现:
too many chunks to print, use verbose if you want to force print
想要看到详细的信息则需要执行:
mongos> sh.status({"verbose":})
或则
mongos> db.printShardingStatus("vvvv")
或则
mongos> printShardingStatus(db.getSisterDB("config"),)
四、测试 :对dba库的account集合进行测试,随机写入,查看是否分散到3个分片中。
判断是否为shard:db.runCommand({isdbgrid:1})
mongos> db.runCommand({isdbgrid:})
{ "isdbgrid" : , "hostname" : "mongo3c", "ok" : }
通过一个python脚本进行随机写入:分别向A、B 2个mongos各写入10万条记录。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
#随即写MongoDB Shard 测试 import pymongo
import time
from random import Random
def random_str(randomlength=8):
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
str+=chars[random.randint(0, length)]
return str def inc_data(conn):
db = conn.dba
# db = conn.test
collection = db.account
for i in range(100000):
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(15):
str+=chars[random.randint(0, length)]
string = str
collection.insert({"name" : string, "age" : 123+i, "address" : "hangzhou"+string}) if __name__ =='__main__':
conn = pymongo.MongoClient(host='192.168.200.A/B',port=30000) StartTime = time.time()
print "===============$inc==============="
print "StartTime : %s" %StartTime
inc_data(conn)
EndTime = time.time()
print "EndTime : %s" %EndTime
CostTime = round(EndTime-StartTime)
print "CostTime : %s" %CostTime
查看是否分片:db.collection.stats()
mongos> db.account.stats() #查看集合的分布情况
...
...
"shards" : {
"shard0000" : {
"ns" : "dba.account",
"count" : ,
"size" : ,
...
...
"shard0001" : {
"ns" : "dba.account",
"count" : ,
"size" : ,
...
...
"shard0002" : {
"ns" : "dba.account",
"count" : ,
"size" : ,
...
...
上面加粗部分为集合的基本信息,可以看到分片成功,各个分片都有数据(count)。到此MongoDB分片集群搭建成功。
++++++++++++++++++++++++++++++++++++++++++++++++
感兴趣的同学可以看下面这个比较有趣的现象:
#在写之前分片的基本信息:
mongos> sh.status()
--- Sharding Status ---
...
...
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" }
dba.account
shard key: { "name" : }
chunks:
shard0000
{ "name" : { "$minKey" : } } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, ) #可以看到这里片键的写入,都是写在shard0000里面的。
#在写期间的分片基本信息:
mongos> sh.status()
--- Sharding Status ---
...
...
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" }
dba.account
shard key: { "name" : }
chunks: #数据块分布
shard0000
shard0001
shard0002
{ "name" : { "$minKey" : } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(, )
{ "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(, )
{ "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, ) #可以看到片键写入的基本分布
#在写完成后的基本信息:
mongos> sh.status()
--- Sharding Status ---
...
...
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" }
dba.account
shard key: { "name" : }
chunks: #数据块分布
shard0000
shard0001
shard0002
{ "name" : { "$minKey" : } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(, )
{ "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : shard0000 Timestamp(, )
{ "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(, )
{ "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(, )
{ "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, ) #最后片键写入的分布
上面加粗的信息对比上看到,本来在每个分片上都只有一个块,最后在shard0000、shard0002上有2个块,被拆分了。shard0001不变。这是因为mongos在收到写请求的时候,会检查当前块的拆分阀值点。到达该阀值的时候,会向分片发起一个拆分的请求。例子中shard0000和shard0002里的块被拆分了。分片内的数据进行了迁移(有一定的消耗),最后通过一个均衡器来对数据进行转移分配。所以在写入途中要是看到一个分片中集合的数量变小也是正常的。
balancer: #均衡器
Currently enabled: yes
Currently running: yes #正在转移
Balancer lock taken at Fri Jul :: GMT+ (CST) by mongo2::::Balancer:
均衡器:均衡器负责数据迁移,周期性的检查分片是否存在不均衡,如果不存在则会开始块的迁移,config.locks集合里的state表示均衡器是否找正在运行,0表示非活动状态,2表示正在均衡。均衡迁移数据的过程会增加系统的负载:目标分片必须查询源分片的所有文档,将文档插入目标分片中,再清除源分片的数据。可以关闭均衡器(不建议):关闭会导致各分片数据分布不均衡,磁盘空间得不到有效的利用。
mongos> sh.setBalancerState(false) #关闭自动均衡器,手动均衡,打开:sh.setBalancerState(true)
mongos> db.settings.find() #查看均衡器状态
{ "_id" : "balancer", "stopped" : true }
可以为均衡器设置一个均衡时间窗口:activeWindow
mongos> db.settings.update({"_id":"balancer"},{"$set":{"activeWindow":{"start":"08:00","stop":"02:00"}}},true)
WriteResult({ "nMatched" : , "nUpserted" : , "nModified" : })
mongos> db.settings.find({"_id":"balancer"})
{ "_id" : "balancer", "stopped" : false, "activeWindow" : { "start" : "08:00", "stop" : "02:00" } }
上面说明:均衡只会在早上8点到凌晨2点进行均衡操作。均衡器是以块的数量作为迁移指标,而非数据大小,块的大小默认是64M,可以修改:(config.settings)
mongos> db.settings.find()
{ "_id" : "chunksize", "value" : }
mongos> db.settings.save({"_id":"chunksize","value":})
WriteResult({ "nMatched" : , "nUpserted" : , "nModified" : })
mongos> db.settings.find()
{ "_id" : "chunksize", "value" : }
上面把块的默认大小改成了32M,除了通过均衡器自动迁移外,还可以手动迁移数据:sh.moveChunk("db.collection",{块地址},"新片名称")
mongos> db.chunks.find({"_id" : "abc.account-name_\"wPeFnJEvendSTbH\""}).pretty() #先到config.chunks上任意找一个块
{
"_id" : "abc.account-name_\"wPeFnJEvendSTbH\"",
"lastmod" : Timestamp(, ),
"lastmodEpoch" : ObjectId("55a52ff1fdd9a605a0371327"),
"ns" : "abc.account",
"min" : {
"name" : "wPeFnJEvendSTbH" #被移动的块
},
"max" : {
"name" : { "$maxKey" : }
},
"shard" : "shard0000" #原先所在的分片
}
mongos> sh.moveChunk("abc.account",{"name" : "wPeFnJEvendSTbH"},"mablevi") #把abc.account集合中包含name(片键)为""的快迁移到mablevi分片中
{ "millis" : , "ok" : }
mongos> db.chunks.find({"_id" : "abc.account-name_\"wPeFnJEvendSTbH\""}).pretty()
{
"_id" : "abc.account-name_\"wPeFnJEvendSTbH\"",
"lastmod" : Timestamp(, ),
"lastmodEpoch" : ObjectId("55a52ff1fdd9a605a0371327"),
"ns" : "abc.account",
"min" : {
"name" : "wPeFnJEvendSTbH"
},
"max" : {
"name" : { "$maxKey" : }
},
"shard" : "mablevi" #已被迁移到新片
}
上面是手动移动数据的操作,数据被移动。 要是块超出了64M限制【原因是片键没选好(日期、状态值等),导致一个块无限增大】,则无法进行自动均衡,无法分块。有2个办法:1是加大块的大小(setting),2是拆分sh.splitAt()(推荐)。
所以要是遇到分片写入比单点写入慢就是因为分片路由服务(mongos)需要维护元数据、数据迁移、路由开销等。
++++++++++++++++++++++++++++++++++++++++++++++++
五、高可用:Sharding+Replset
上面的分片都是单点的,要是一个分片坏了,则数据会丢失,利用之前减少的副本集,能否把副本集加入到分片中?下面就来说明下。
1)添加副本集分片服务器(mmm副本集名称):这里测试就只对一个分片加副本集,要实现完全的高可用就需要对所有分片加副本集,避免单点故障
一个普通的副本集:
mmm:PRIMARY> rs.status()
{
"set" : "mmm",
"date" : ISODate("2015-07-10T16:17:19Z"),
"myState" : ,
"members" : [
{
"_id" : ,
"name" : "192.168.200.245:27017",
"health" : ,
"state" : ,
"stateStr" : "SECONDARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2015-07-10T16:16:43Z"),
"lastHeartbeat" : ISODate("2015-07-10T16:17:17Z"),
"lastHeartbeatRecv" : ISODate("2015-07-10T16:17:18Z"),
"pingMs" : ,
"syncingTo" : "192.168.200.25:27017"
},
{
"_id" : ,
"name" : "192.168.200.25:27017",
"health" : ,
"state" : ,
"stateStr" : "PRIMARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2015-07-10T16:16:43Z"),
"self" : true
},
{
"_id" : ,
"name" : "192.168.200.245:37017",
"health" : ,
"state" : ,
"stateStr" : "SECONDARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2015-07-10T16:16:43Z"),
"lastHeartbeat" : ISODate("2015-07-10T16:17:17Z"),
"lastHeartbeatRecv" : ISODate("2015-07-10T16:17:17Z"),
"pingMs" : ,
"syncingTo" : "192.168.200.25:27017"
}
],
"ok" :
}
现在需要把这个副本集加入到分片中:
mongos> sh.addShard("mmm/192.168.200.25:27017,192.168.200.245:27017,192.168.200.245:37017") #加入副本集分片
{ "shardAdded" : "mmm", "ok" : } mongos> sh.status()
--- Sharding Status ---
...
...
shards:
{ "_id" : "mmm", "host" : "mmm/192.168.200.245:27017,192.168.200.245:37017,192.168.200.25:27017" }
{ "_id" : "shard0000", "host" : "192.168.200.51:40000" }
{ "_id" : "shard0001", "host" : "192.168.200.52:40000" }
{ "_id" : "shard0002", "host" : "192.168.200.53:40000" }
balancer:
Currently enabled: yes
Currently running: no
Failed balancer rounds in last attempts:
Migration Results for the last hours:
: Success
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" }
dba.account
shard key: { "name" : }
chunks:
mmm
shard0000
shard0001
shard0002
{ "name" : { "$minKey" : } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(, )
{ "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : mmm Timestamp(, )
{ "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(, )
{ "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(, )
{ "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, )
{ "_id" : "abc", "partitioned" : false, "primary" : "shard0000" } #未设置分片
上面加粗部分表示副本集分片已经成功加入,并且新加入的分片会分到已有的分片数据。
mongos> db.account.stats()
...
...
"shards" : {
"mmm" : {
"ns" : "dba.account",
"count" : , #后加入的分片得到了数据
"size" : ,
"avgObjSize" : ,
"storageSize" : ,
"numExtents" : ,
"nindexes" : ,
"lastExtentSize" : ,
"paddingFactor" : ,
"systemFlags" : ,
"userFlags" : ,
"totalIndexSize" : ,
"indexSizes" : {
"_id_" : ,
"name_1" :
},
"ok" :
},
...
...
2)继续用python脚本写数据,填充到副本集中
由于之前的副本集是比较老的版本(2.4),所以在写入副本集分片的时候报错:
mongos> db.account.insert({"name":"UavMbMlfsz1OFrz"})
WriteResult({
"nInserted" : ,
"writeError" : {
"code" : ,
"errmsg" : "write results unavailable from 192.168.200.25:27017 :: caused by :: Location28563 cannot send batch write operation to server 192.168.200.25:27017 (192.168.200.25)"
}
})
太混蛋了,错误提示不太人性化,搞了半天。所以说版本一致性还是很重要的。现在重新开了一个副本集:
mablevi:PRIMARY> rs.status()
{
"set" : "mablevi",
"date" : ISODate("2015-07-10T18:22:36.761Z"),
"myState" : ,
"members" : [
{
"_id" : ,
"name" : "192.168.200.53:50000",
"health" : ,
"state" : ,
"stateStr" : "PRIMARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2015-07-10T18:20:12Z"),
"electionTime" : Timestamp(, ),
"electionDate" : ISODate("2015-07-10T18:11:50Z"),
"configVersion" : ,
"self" : true
},
{
"_id" : ,
"name" : "192.168.200.53:50001",
"health" : ,
"state" : ,
"stateStr" : "SECONDARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2015-07-10T18:20:12Z"),
"lastHeartbeat" : ISODate("2015-07-10T18:22:36.737Z"),
"lastHeartbeatRecv" : ISODate("2015-07-10T18:22:36.551Z"),
"pingMs" : ,
"syncingTo" : "192.168.200.53:50000",
"configVersion" :
},
{
"_id" : ,
"name" : "192.168.200.53:50002",
"health" : ,
"state" : ,
"stateStr" : "SECONDARY",
"uptime" : ,
"optime" : Timestamp(, ),
"optimeDate" : ISODate("2015-07-10T18:20:12Z"),
"lastHeartbeat" : ISODate("2015-07-10T18:22:36.742Z"),
"lastHeartbeatRecv" : ISODate("2015-07-10T18:22:36.741Z"),
"pingMs" : ,
"syncingTo" : "192.168.200.53:50001",
"configVersion" :
}
],
"ok" : ,
"$gleStats" : {
"lastOpTime" : Timestamp(, ),
"electionId" : ObjectId("55a00ae6a08c789ce9e4b50d")
}
}
把之前的副本集分片删除了,如何删除见下面3)。
新的副本集加入分片中:
mongos> sh.addShard("mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002")
{ "shardAdded" : "mablevi", "ok" : } mongos> sh.status()
--- Sharding Status ---
...
...
shards:
{ "_id" : "mablevi", "host" : "mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002" }
{ "_id" : "shard0000", "host" : "192.168.200.51:40000" }
{ "_id" : "shard0001", "host" : "192.168.200.52:40000" }
{ "_id" : "shard0002", "host" : "192.168.200.53:40000" }
...
...
dba.account
shard key: { "name" : }
chunks:
mablevi
shard0000
shard0001
shard0002
{ "name" : { "$minKey" : } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(, )
{ "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : mablevi Timestamp(, ) #新加入的分片得到数据
{ "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(, )
{ "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(, )
{ "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, )
{ "_id" : "abc", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "mablevi", "partitioned" : false, "primary" : "shard0001" }
继续用python写入操作:
mongos> db.account.stats()
{
...
...
"shards" : {
"mablevi" : {
"ns" : "dba.account",
"count" : ,
"size" : ,
...
...
副本集的分片被写入了47240条记录。此时把副本集分片的Primary shutdown掉,再查看:
mongos> db.account.stats()
{
"sharded" : true,
"code" : ,
"ok" : ,
"errmsg" : "exception: can't connect to new replica set master [192.168.200.53:50000], err: couldn't connect to server 192.168.200.53:50000 (192.168.200.53), connection attempt failed" #由于副本集的Primary被shutdown之后,选举新主还是要几秒的时间,期间数据不能访问,导致分片数据也不能访问
}
mongos> db.account.stats()
...
...
"shards" : {
"mablevi" : {
"ns" : "dba.account",
"count" : , #副本集新主选举完毕之后,分片数据访问正常。数据没有丢失,高可用得到了实现。
"size" : ,
...
...
要是让副本集分片只剩下一台(Secondary),则分片会报错:
mongos> db.account.stats()
{
"sharded" : true,
"code" : ,
"ok" : ,
"errmsg" : "exception: ReplicaSetMonitor no master found for set: mablevi" #数据不能访问
}
3)删除分片: db.runCommand({"removeshard":"mmm"})
要是觉得分片太多了,想删除,则:
mongos> use admin #需要到admin下面删除
switched to db admin
mongos> db.runCommand({"removeshard":"mmm"})
{
"msg" : "draining started successfully",
"state" : "started", #开始删除,数据正在转移
"shard" : "mmm",
"ok" :
}
mongos> sh.status()
--- Sharding Status ---...
...
shards:
{ "_id" : "mmm", "host" : "mmm/192.168.200.245:27017,192.168.200.245:37017,192.168.200.25:27017", "draining" : true } #删除的分片数据移动到其他分片
{ "_id" : "shard0000", "host" : "192.168.200.51:40000" }
{ "_id" : "shard0001", "host" : "192.168.200.52:40000" }
{ "_id" : "shard0002", "host" : "192.168.200.53:40000" }
...
...
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" }
dba.account
shard key: { "name" : }
chunks:
shard0000
shard0001
shard0002
{ "name" : { "$minKey" : } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(, )
{ "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : shard0000 Timestamp(, )
{ "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(, ) #这里已经没有了被删除分片信息
{ "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(, )
{ "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, )
{ "_id" : "abc", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "mablevi", "partitioned" : false, "primary" : "shard0001" } mongos> db.runCommand({"removeshard":"mmm"}) #再次执行,直到执行成功,要是原来分片的数据比较大,这里比较费时,要是一个主分片则需要执行movePrimary
{
"msg" : "removeshard completed successfully",
"state" : "completed", #完成删除
"shard" : "mmm",
"ok" :
}
mongos> sh.status()
--- Sharding Status ---...
shards: #分片消失
{ "_id" : "shard0000", "host" : "192.168.200.51:40000" }
{ "_id" : "shard0001", "host" : "192.168.200.52:40000" }
{ "_id" : "shard0002", "host" : "192.168.200.53:40000" }
...
...
{ "name" : { "$minKey" : } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(, )
{ "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : shard0000 Timestamp(, )
{ "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(, ) #已经没有了被删除分片的信息
{ "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(, )
{ "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, )
{ "_id" : "abc", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "mablevi", "partitioned" : false, "primary" : "shard0001" }
分片被删除之后,数据被移到其他分片中,不会丢失。要是想让主分片进行转移则(movePrimary):
mongos> db.adminCommand({"movePrimary":"test","to":"shard0001"}) #把test的主分片从shard0000迁移到shard0001
刷新下配置服务器:db.adminCommand({"flushRouterConfig":1})
db.adminCommand({"flushRouterConfig":})
最后来查看下分片成员:db.runCommand({ listshards : 1 })
mongos> use admin #需要进入admin才能执行
switched to db admin
mongos> db.runCommand({ listshards : })
{
"shards" : [
{
"_id" : "shard0000",
"host" : "192.168.200.51:40000"
},
{
"_id" : "shard0001",
"host" : "192.168.200.52:40000"
},
{
"_id" : "shard0002",
"host" : "192.168.200.53:40000"
},
{
"_id" : "mablevi",
"host" : "mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002"
}
],
"ok" :
}
到此已经把MongoDB分片原理、搭建、应用大致已经介绍完。
六、认证分配
上面的所有操作都是在无账号密码下进行的,这样是不安全的,那如何使用账号密码呢?和副本级一样,需要添加KeyFile参数,但是针对上面的三个角色(config、mongos、mongod)账号密码怎么添加呢?官网上已经做了说明:http://docs.mongodb.org/manual/tutorial/enable-authentication-in-sharded-cluster/。下面就对有账号密码认证分片进行相关设置说明。
首先要创建账号(Root角色)和生成一个KeyFile文件,其中mongos 不需要创建账号。
openssl rand -base64 > mongodb-keyfile
chmod mongodb-keyfile
其实这个文件也可以直接用明文,只要保证各个地方指定的文件是同一个就可以了。
1)mongd: 首先在mongod角色的分片成员上生成key file文件,特别注意的是有副本级的分片,再把这个文件分别复制到其他角色的服务器上。再添加参数:
auth = true
keyFile = /usr/local/mongodb-keyfile
2)Config上添加参数:
auth = true
keyFile = /usr/local/mongodb-keyfile
3)mongos上添加参数,因为mongos本来就是从config里加载数据的,所以只需要添加keyfile文件即可,不需要找上面createUser。
keyFile = /usr/local/mongodb-keyfile
最后重启各个服务,再进入mongos里查看:
root@mongo1:/usr/local# mongo --port=
MongoDB shell version: 3.0.
connecting to: 127.0.0.1:/test
mongos> sh.status() #没有认证,没有权限报错。
--14T23::11.800+ E QUERY Error: error: { "$err" : "not authorized for query on config.version", "code" : }
at Error (<anonymous>)
at DBQuery.next (src/mongo/shell/query.js::)
at DBCollection.findOne (src/mongo/shell/collection.js::)
at printShardingStatus (src/mongo/shell/shardingtest.js::)
at Function.sh.status (src/mongo/shell/utils_sh.js::)
at (shell):: at src/mongo/shell/query.js:
mongos> use admin
switched to db admin
mongos> db.auth('dba','dba') #认证
mongos> sh.status() #有权限
--- Sharding Status ---
sharding version: {
"_id" : ,
"minCompatibleVersion" : ,
"currentVersion" : ,
"clusterId" : ObjectId("55a51ef18bd517d4acec5ef9")
}
shards:
{ "_id" : "mablevi", "host" : "mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002" }
{ "_id" : "shard0000", "host" : "192.168.200.51:40000" }
{ "_id" : "shard0001", "host" : "192.168.200.52:40000" }
{ "_id" : "shard0002", "host" : "192.168.200.53:40000" }
balancer:
...
...
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0000" }
{ "_id" : "dba", "partitioned" : true, "primary" : "shard0000" }
dba.account
shard key: { "name" : }
chunks:
mablevi
shard0000
shard0001
shard0002
{ "name" : { "$minKey" : } } -->> { "name" : "9XXqCaBhfhPIXLq" } on : mablevi Timestamp(, )
{ "name" : "9XXqCaBhfhPIXLq" } -->> { "name" : "RWINvgjYYQmbZds" } on : shard0002 Timestamp(, )
{ "name" : "RWINvgjYYQmbZds" } -->> { "name" : "jSPRBNH8rvnzblG" } on : shard0001 Timestamp(, )
{ "name" : "jSPRBNH8rvnzblG" } -->> { "name" : "okmjUUZuuKgftDC" } on : shard0001 Timestamp(, )
{ "name" : "okmjUUZuuKgftDC" } -->> { "name" : { "$maxKey" : } } on : shard0000 Timestamp(, )
七、分片备份、还原
因为分片机制里面会有平衡器来迁移数据,所以各个分片里的数据很可能会移动,所以在备份分片时需要做:
①:先停止平衡器的工作,并检查没有chunk move动作,保证dump的时候没有进行数据迁移。
mongos> sh.stopBalancer()
②:锁定数据库,保证数据没有写入:在各个分片上和配置服务器上执行。
> db.fsyncLock()
{
"info" : "now locked against writes, use db.fsyncUnlock() to unlock",
"seeAlso" : "http://dochub.mongodb.org/core/fsynccommand",
"ok" :
}
③:执行备份操作,备份各个分片服务器和配置服务器。
mongodump -udba -p12345 -d dba_test --authenticationDatabase admin -o backup/
④:解锁数据库,备份完成之后在分片和配置服务器上解锁数据库,允许修改。
> db.fsyncUnlock()
{ "ok" : , "info" : "unlock completed" }
当数据库出现问题,需要还原的时候,需要还原各个分片和配置服务器,并且重启MongoDB实例。还原数据库需要做:
①:还原各个分片和配置服务器。
mongorestore --host=127.0.0.1 --port= -udba -p12345 -d dba_test --authenticationDatabase admin --drop backup/dba_test
②:重启各个实例
总结:
分片很好的解决了单台服务器磁盘空间、内存、cpu等硬件资源的限制问题,把数据水平拆分出去,降低单节点的访问压力。每个分片都是一个独立的数据库,所有的分片组合起来构成一个逻辑上的完整的数据库。因此,分片机制降低了每个分片的数据操作量及需要存储的数据量,达到多台服务器来应对不断增加的负载和数据的效果。后面文章还会继续对分片的其他方面进行说明介绍。
参考文档:
说明:http://docs.mongodb.org/manual/core/sharding-introduction/
配置:http://docs.mongodb.org/manual/tutorial/deploy-shard-cluster/
应用:http://www.caiyiting.com/blog/2014/replica-sets-sharding-realization.html