MongoDB权威指南学习笔记(3)--复制和分片

Mongo

复制和分片

创建副本集

建立副本集

使用mongo --nodb选项启动mongo shell,启动shell但是不连接到任何mongod

$ mongo --nodb

创建副本集

replicaSet=new ReplSetTest({
    "nodes":3
})

会创建一个包含三个服务器的副本集:一个住服务器和两个备份服务器

指定命令使mongod服务器启动

replicaSet.startSet()
replicaSet.initiate()

然后会启动三个mongod进行,分别运行在31000\31001和31002端口

连接到运行在31000端口的mongod

conn1=new Mongo("127.0.0.1:31000")

> primary=conn1.getDB("test")

在连接到主节点的连接上执行isMaster命令,可以看到副本集的状态

> primary.isMaster()

在主节点插入一些文档,

备份节点可能会落后于主节点,可能没有最新写入的数据,所以备份节点在默认情况下会拒绝读取请求,以防应用意外拿到过期的数据。当在备份节点做查询时,可能会得到一个错误提示,说当前节点不是主节点

如果希望从备份节点读取数据,需要设置“从备份节点读取数据没有问题”的标示

conn2.setSlaveOk()

然后就可以在该连接的备份节点中读取数据

不能对备份节点执行写操作,备份节点只能通过复制功能写入数据,不接受客户端的写入请求
具有自动故障转移的功能,如果主节点挂了,其中一个备份节点会自动选举为主节点

配置副本集

首先需要为副本集选定一个名字,使用--replset name选项重启server

$ mongod --replSet spock -f mongod.conf --fork

然后使用同样的replset和标识符stock再启动两个mongod服务器作为副本集中的其他成员

# server2
$ mongod --replSet spck -f mongo.conf --fork

#server3
$ mongod --replSet spck -f mongo.conf --fork

只要将后两个成员添加到副本集中,它们就会自动克隆第一个成员的数据

将replSet选项添加到每个成员的mongod.conf文件中,启动时就会自动使用这个选项

为了让每个mongod能够知道批次的存在,需要创建一个配置文件,在配置文件中列出每一个成员,并且将配置文件发送给server-1,然后server-1会负责将配置文件传播给其他成员

在shell中创建一个如下所示的文档

config={
    "_id":"spock",
    "members"[
        {
            "_id":0,
            "host":"server-1:27017"
        },
        {
            "_id":1,
            "host":"server-2:27017"
        },
        {
            "_id":2,
            "host":"server-3:27017"
        }
    ]
}
  • 第一个_id字段就是副本集名称
  • 将host字段的值修改为实际ip

这个config对象就是副本集的配置,现在需要将其发送给其中一个副本集成员,连接到一个有效的服务器,使用config对象对副集进行初始化

// 连接到server1
db=(new Mongo("server-1:27017")).getDB("test")

// 初始化副本集
rs.initiate(config)

server-1会解析这个配置对象,然后向其他成员发送消息,提醒他们使用新的配置,所有车公园配置完成之后,他们会自动选出一个主节点,然后就可以正常处理请求了

rs辅助函数

rs是一个全局变量,其中包含与复制相关的函数

网络注意事项

副本集内的每个成员都必须能够连接到其他所有成员

副本集的配置中不应该使用localhost作为主机

修改副本集配置

可以随时修改副本集的配置,可以添加或者删除成员,也可以修改已有成员

//向副本集中添加成员
rs.add("server-4:27017")

// 删除成员
rs.remove("server-1:27017")

// 查看配置
rs.config()

// 重新配置配置文件
rs.reconfig(config)

副本集的组成

同步

复制用于在多台服务器之间备份数据,mongo的复制功能时使用操作日志oplog实现的,操作日志包含了主节点的每一次写操作。

如果某个备份节点由于某些原因挂掉,当他重新启动后,就会自动从oplog中最后一个操作开始进行同步

初始化同步

副本集中的成员启动之后,就会检查自身状态,确定是否可以从某个成员那里进行同步,如果不行的话,它会尝试从副本的另一个成员那里进行完整的数据复制,这个过程就是初始化同步

处理陈旧数据

如果备份节点远远落后同步源当前的操作,那么这个备份节点就是陈旧的。如果备份节点曾静停机过,写入量炒股哟自身处理能力,或者时有太多的读请求,这些情况都有可能导致备份节点陈旧

当一个节点陈旧时候,它会查询副本中的其他成员,如果成员的oplog足够详尽,可以用于处理那些落下的操作,就从这个成员进行同步,如果都没有参考价值,那么这个成员的复制操作就会中止,这个成员需要重新进行完全同步。

心跳

为了维护集合的最新视图,每个成员每隔2s就会向其他成员发送一个心跳请求。用于检查每个成员的状态

成员状态

  • STARTUP:成员刚启动时出于这个状态,会尝试加在成员的副本集配置,加在成功后,就进入STARTUP2状态
  • STARTUP2:整个初始化同步过程都处理这个状态,但是如果在普通成员上,这个状态只会持续几秒种。在这个状态下,mongodb会创建几个线程,用于处理复制和选举,然后切换到RECOVERING状态
  • 表示成员运转正常,但是暂时还不能处理读取请求
  • ARBITER:在正常的操作中,仲裁者应该处理ARBITER状态

系统出现问题时会处理下面这些状态:

  • DOWN:如果一个正常运行的成员变得不可达,它就出于DOWN状态
  • UNKNOWN:如果一个成员无法到达其他任何成员,其他成员就无法知道它处理什么状态,会将其报告为UNKOWN状态
  • REMOVE:当成员被移出副本集时,就出于这个状态
  • ROLLBACK:当成员正在进行数据回滚,就出于ROLLBACK状态,回滚过程结束时,服务器会转换为RECOVERING状态,然后成为备份节点
  • FATAL:如果一个成员发生了不可挽回的错误,也不再尝试恢复正常的话,它就出于FATAL状态

选举

当一个成员无法到达主节点时,它就会申请被选举为主节点。希望呗选举为主节点的成员,会向它能到达的所有成员发送通知如果这个成员不符合候选人要求,其他成员可能知道相关原因

假如没有反对的理由,其他成员就会对这个成员进行选举投票。如果这个成员得到副本大多数赞成票,它就会选举成功,会转到主节点状态。如果 达不到大多数要求,那就出于备份节点状态,之后还可以再次申请呗选举为主节点,主节点会一致出于主节点状态。除非它不在满足大多数的要求或者挂了而退位。

从应用程序连接副本集

客户端到副本集的连接

从应用程序的角度,使用副本集与使用单台服务器一致。默认情况下,驱动程序连接到主节点,并且将所有路由都路由到主节点。应用程序可以像是有那个单台服务器一样进行读写,副本集会在后台处理热备份

连接副本集与连接单台服务器非常想,一个常用的连接字符串如下:

mongodb://server-1:27017,server-2:27017

当主节点挂掉之后,驱动程序会尽快自动找到新的主节点,在选举过程中,主节点可能会暂时不可用,如果没有可达的成员能够成为主节点,主节点可能长时间不可用

等待写入复制

使用getLastError命令检查写入是否成功,也可以使用这个命令确保写入操作呗复制到备份节点,参数w会强制要求getLastError等待,一直到给定数量的成员都执行完了最后的写入操作。

db.runCommand({
    "getLastError":1,
    "w":"majority"
})

可以指定wtimeout的值设置超时时间,如果这个超过这个时间还没有返回,就会返回失败

设置超时时间为1s

db.runCommand({
    "getLastError":1,
    "w":"majority",
    "wtimeout":1000
})

分片

分片是指将数据拆分,将其分散存放在不同的机器上的过程。

几乎所有数据库都能进行手动分片,但mongo支持自动分片,可以使数据库架构对应用程序不可见,也可以简化系统管理。对应用来说,和使用单机mongo服务器一样。

在分片之前需要先执行mongos进行一次路由过程

快速建立一个简单集群

使用-nodb选项启动mongo shell

$ mongo --nodb

使用ShardingTest创建集群

cluster=new ShardingTest({
    "shards":3,
    "chunksize":1
})

会创建包含三个切片的集群,分别运行在30000,30001,30002端口,默认情况下ShardingTest会在30999端口启动mongos

连接到mongos使用集群

db=(new Mongo("127.0.0.1:30999")).getDB("test")

接下来和使用单机服务器完全一样

使用sh.status()可以查看集群的状态,分片摘要信息、数据库摘要信息、集合摘要信息

主分片是为每个数据库随机选择的,所有数据都会位于主分片上。目前还不能自动将数据分发到不同的分片上,因为它不知道你希望如何分发数据。对每一个集合,必须明确指定,应该如何分发数据。

要求一个集合分片,首先要对这个集合的数据库启用分片,执行下列命令

sh.enableSharding("test")

对集合分片时,要 选择一个片键。片键时集合的一个键,mongodb根据这个键拆分数据。(例如,如果选择基于“username”进行分片,mongo会根据不同的用户名进行分片) 选择片键可以认为时选择集合中的数据的顺序。它与索引时个相似的概念;随着集合的不断增长,片键就会成为集合上最重要的索引。只有呗索引过的键才能作为片键

在启用分片时,先在希望作为片键的键上创建索引

db.users.ensureIndex({
    "username":1
})

然后对集合进行分片

sh.shardCollection("test.users",{
    "username":1
})

集合会被分为读个数据块,每一个数据块都是集合的一个数据子集

包含片键的查询能够直接被发送到目标分片或者是集群分片的一个子集,这样的查询叫定向查询

有些查询必须被发送到所有分片,这样的查询叫分散-聚集查询,mongo将查询分散到所有分片上,然后将各个分片的查询结果聚集起来。

关闭集群

cluster.stop()

配置分片

何时分片

通常不必太早分片,因为分片不仅会增加部署的操作复杂度,还要求作出设计决策,而该决策以后很难再改。另外最好也不要在系统运行太久之后在分片。

分片用来:

  • 增加可用RAM
  • 增加可用磁盘空间
  • 减轻单台服务器的负载
  • 处理单个mongod无法承受的吞吐量

启动服务器

配置服务器

配置服务器相当于集群的大脑,保存着集群和分片的元数据,即各分片包含哪些数据的信息,因此,应该首先建林配置服务器

启动配置服务器

$ mongod --configscr --dbpath /var/lib/mongodb -f /var/lib/config/mongod.conf
  • 启动配置服务器时,不要使用--replSet选项:配置服务器不是副本集成员
  • --configscr 指定mongod为新的配置服务器,该配置将mongod的默认监听端口改为27019,并吧默认的数据目录改为/data/confgdb(可使用--port和--dbpath选项修改这两项配置)

mongos进程

当服务器出于运行状态后,启动一个mongos进程提供应用程序连接。mongos进程需要知道配置服务器的地址,所以必须使用--configdb选项启动mongos

$ mongos -configdb config-1:279019,config-2:279019.config-3:279019 -f /var/lib/mongos.conf

将副本集转换为分片

假设我们已经拥有了一个副本集

如果已经有一个使用中的副本集,该副本集会成为第一个分片。为了将副本集转换为分片,需告知mongos副本集名称和副本集成员列表

例如在server-1到server-5上有一个名为spock的副本集,可连接到mongos并运行:

sh.addShard("spock/server-1:27017,server-2:27017,server-4:27017")

可在参数中指定副本集的所有成员。如果运行sh.status(),可发现mongodb已经找到了其他的副本集成员

也可以创建但mongod服务器的分片(而不是副本集分片),直接在addShard()中指定单个mongod的主机名和端口

sh.addShard("some-server:27017")

数据分片

除非明确指定规则,否则mongodb不会自动对数据进行拆分,如有必要,必须明确告知数据库和集合。

假设我们希望对music数据库中的artists集合按照name键进行分片。首先,对music数据库启用分片

db.enableSharding("music")

对数据库分片是对集合分片的先决条件

对数据库启用分片之后,就可以使用shardCollection()命令对集合分片了

sh.shardCollection("music.artists",{
    "name":1
})
  • 如果对已存在的集合进行分片,那么name键上必须有索引
  • 如果进行分片的集合还不存在,mongos会自动在片键上创建索引

均衡器

均衡器负责数据的迁移,它会周期性的检查分片-是否存在不均衡,如果存在,就会开始快的迁移

选择片键

检查使用情况

对集合进行分片,要选择一或两个字段用于拆分数据。这个键就叫做片键

数据分发

数据分发有三种:

  • 升序片键
  • 随机分发片键
  • 基于位置的片键

升序片键

升序片键类似于"date"字段或者是objectId,是一种会随着时间稳定增长的字段。

随机分发的片键

随机分发的键可以是用户名、邮件地址、uuid、md5或者是数据集中其他没有规律的键

基于位置的片键

基于位置的片键可以是用户的ip、经纬度或者地址。位置片键不必与实际的物理位置字段相关。数据会根据这个位置进行分组。

片键策略

散列片键

如果追求的是数据加在速度的极值,那么散列片键时最佳选择。散列片键可使其他任何键随机分发。所以,如果打算在大量查询中使用升序键,但又同时希望吸入数据随机分发的话,散列片键会是个好选择。

弊端时无法使用散列片键作为指定目标的范围查询。

创建散列片键,首先要创建散列索引

db.users.ensureIndex({
    "username":"hashed"
})

然后对集合分片

db.shardCollection("app.users",{
    "username":"hashed"
})

局限性:

  • 不能使用enique选项
  • 不能使用数组字段
  • 浮点型的值会先呗取整,然后才会进入散列,所有1和1.99999会得到相同的散列值

GridFS的散列片段

GridFS集合通常非常适合做分片,因为它们包含大量的文件数据

files_id字段上创建散列索引,则每个文件都会随机分发到集群中,但是一个文件只能呗包含在一个单一的块中,这时非常好的

为了实现这种策略,需要在{"files_id":"hashed"}创建新的索引,然后依据这个字段对集合分片

片键规则和指导方阵

片键限制

片键不可以是数组,向片键插入数据值也是不被允许的

文档一旦插入,其片键值就无法修改了。因此应该选择不会呗改变的字段,或者时很少发生变化的字段

大多数特殊类型的索引都不能用作片键

片键的势

不管片键时跳跃增长还是稳定增长,选择一个值发生变化的键时非常重要的。与索引一样,分片在势比比较高的字段性能更佳

注:

  • 上述测试在MongoDB 3.4.3-8-g05b19c6中成功
  • 上述文字皆为个人看法,如有错误或建议请及时联系我

推荐阅读更多精彩内容