DBA MongoDB 分片集群

架构探究

架构图

​ MongoDB分片集群与MySQL中的水平分表类似。

​ 以下是一个较为完美的分片集群部署策略图:

image-20210319225746152

集群组成

​ 名词解释:

  • mongos :路由节点,提供集群单一入口,转发application的请求至所有分片节点上,上图中3个分片节点均是为了保证高可用又做了复制集
  • config :配置节点,存储分片的配置策略,如分片开始位置,分片结束位置等
  • shard :分片节点,以复制集为单位,对于分片节点来说,横向扩展最大可支持1024个,每个分片节点上的数据不允许重复,所有分片在一起才能完整的进行工作

​ 分片集群由多个分片节点+配置节点+路由节点构成,特点如下:

  1. 分片集群对application是透明的,没有任何特殊处理

  2. 且对于数据来说会有自动均衡策略

  3. 如果要对分片集群进行扩容,可在线上直接进行扩容而无需下线

  4. MongoDB当前提供3种分片方式

分片相关

分片策略

​ 分片策略如下:

  • 基于范围,范围查询性能良好,侧重于读的性能,但是可能造成数据分布不均匀
  • 基于Hash,数据分布均匀,侧重于写的性能,但是范围查询效率低
  • 基于zones / tag,适用于大范围的全球性业务

​ 基于范围分片的图示:

img

​ 基于Hash分片的图示:

img

​ 基于Zones:

img

分片计算

​ 如何计算当前业务需要多少个分片?下面是计算法则:

  • 使用存储数据总量 / 单个服务器可挂载的容量 如:8TB / 2TB = 4
  • 使用工作集的大小 / 单个服务器的内容容量*0.6(不可能全部占满) 如:400GB / (256G * 0.6) = 3
  • 使用最高并发总数 / 单个服务器平均并发量*0.7(一个复制集群是有损耗的) 如:3000 / (9000 * 0.7) = 6

​ 最终的分片数量采取3个结果中的最大值: max(4, 3, 6) = 6

​ 对于分片的数据来说,每个分片节点的数据量都不应该超过3TB

​ 对于分片的索引来说,必须保证常用的索引字段能够存放至内存中(查看文档索引,结合物理内存大小做计算)

分片名词

​ 一些分片中常见的概念性名词,概念由小到大:

  • 片键 shard key :文档中的一个字段,由于MongoDB的分片类似于水平分表,所以我们必须指定拆分的字段列
  • 文档 doc :包含shard key的一行数据(对应到关系型数据库中,一个文档就是一行记录)
  • 块 Chunk : 一个块包含多个文档,每个块的大小为64MB
  • 分片 Shard : 一个分片节点中包含多个块,没有大小限制,但应该尽量保证单个分片的数据量在3TB下
  • 集群 Cluster : 一个分片集群中包含多个分片节点,通常情况下一个分片集群也必须是一个复制集群(保证高可用)

片键选择

​ 在选择片键时,要遵循以下几点原则,为了方便描述我均采用关系型数据库的概念来举例:

  1. 取值基数大:如果对一个表来说,水平拆分时如果按照gender字段进行拆分,那么即使拆分成多个表,单表的数据量级依旧很大,最好的选择是根据id进行拆分
  2. 取值分布均匀:如果对一个学生表来进行水平拆分,片键选择是年龄,年龄的基数虽然可以达到1-100,但是要注意业务场景,学生表的主要数据年龄为12-16岁(中学),这依然会造成单表的数据量级大的问题
  3. 片键应该利于分散写,集中读:如果片键选择不合理,对于写来说则可能造成写入的数据的Chunk块先到第一个分片节点中,发现位置不对再向第二个分片节点进行迁移的情况,造成性能损耗,多做了无用功。而对于读来说则可能会导致扫描所有分片节点的操作,不能准确定位,造成查询效率低的问题

​ 以下用几种情况来举例MongoDB中片键的选择案例,这里与关系型数据库有较大的差异,建议不要做横向对比。

​ 举例1,按照_id进行范围分片:

  • 从基数的角度来看,使用_id进行分片,因为_id不会重复,所以是合理的

  • 从写的角度看,由于_id总是递增的,新插入的数据总会到达最后的一个分片节点,是合理的

  • 从读的角度看,用于MongoDB的_id与MySQL的id不同,因此没有人会用_id作为查询条件,这可能导致会扫描所有分片节点,读的性能极低,是不合理的

​ 举例2,按照phone进行hash分片:

  • 从基数的角度来看,phone永远不会重复,是合理的
  • 从写的角度来看,使用hash分片,能够准确的将数据插入到对应的分片节点中,是合理的
  • 从读的角度来看,使用phone作为查询条件虽然可以很快的拿出单个用户的数据,但是对于范围查询来讲效率会偏慢,这是不合理的

​ 举例3,按照用户的id(不是_id)和phone进行分片:

  • 从基数的角度来看,phone与用户id不会重复,是合理的
  • 从写的角度来看,和上面一样,是合理的
  • 从读的角度来看,无论是使用用户id或者phone进行读取,都能较快的拿出数据,是合理的

集群搭建

搭建环境

​ 3台Centos7.3的虚拟机,4核心8GB的RAM

geekdemo1:Shard 1 Primary 27010、Shard 2 Primary 27011、Config 1 27019、mongos 27017
geekdemo2:Shard 1 Sencondary 27010、Shard 2 Sencondary 27011、Config 2 27019
geekdemo3:Shard 1 Sencondary 27010、Shard 2 Sencondary 27011、Config 3 27019

​ 集群规划表示:

member1 member2 member3 member4 member5 member6
shard1
shard2
config
mongos

域名解析

​ 在 3 台虚拟机上分别执行以下 3 条命令,注意替换实际 IP 地址

echo “192.168.1.1 geekdemo1 member1.example.com member2.example.com” >> /etc/hosts
echo “192.168.1.2 geekdemo2 member3.example.com member4.example.com” >> /etc/hosts
echo “192.168.1.3 geekdemo3 member5.example.com member6.example.com” >> /etc/hosts

分片目录

​ 在各服务器上创建数据目录,我们使用 /data,请按自己需要修改为其他目录:

  • 在 member1/member3/member5 上执行以下命令:

    mkdir -p /data/shard1
    mkdir -p /data/config
    
  • 在 member2/member4/member6 上执行以下命令:

    mkdir -p /data/shard2
    mkdir -p /data/mongos
    

复制集

​ 创建第一个分片用的复制集,在 member1/member3/member5 上执行以下命令::

mongod –bind_ip 0.0.0.0 –replSet shard1 –dbpath /data/shard1 –logpath /data/shard1/mongod.log –port 27010 –fork –shardsvr –wiredTigerCacheSizeGB 1

​ 初始化第一个分片复制集:

mongo --host member1.example.com:27010

rs.initiate({
  _id: "shard1",
  "members":[
    {
      "_id": 0,
      "host":"member1.example.com:27010"
    },
    {
      "_id": 1,
      "host":"member3.example.com:27010"
    },
    {
      "_id": 2,
      "host":"member5.example.com:27010"
    }
  ]
});

​ 创建config server复制集,在 member1/member3/member5 上执行以下命令:

mongod –bind_ip 0.0.0.0 –replSet config –dbpath /data/config –logpath /data/config/mongod.log –port 27019 –fork –configsvr –wiredTigerCacheSizeGB 1

​ 初始化config server复制集:

mongo --host member1.example.com:27019

rs.initiate({
  _id: "config",
  "members":[
    {
      "_id": 0,
      "host":"member1.example.com:27019"
    },
    {
      "_id": 1,
      "host":"member3.example.com:27019"
    },
    {
      "_id": 2,
      "host":"member5.example.com:27019"
    }
  ]
});

分片配置

​ 在第一台机器geekdemo1上搭建mongos,指向config server:

mongos --bind_ip 0.0.0.0 --logpath /data/mongos/mongos.log --port 27017 --fork --configdb config/member1.example.com:27019,member3.example.com:27019,member5.example.com:27019

​ 链接到mongos,添加分片:

mongo --host member1.example.com:27017

mongos> sh.addShard("shard1/member1.exmple.com:27010,member3.exmple.com:27010,member5.exmple.com:27010");

​ 链接到mongos,创建分片集合创建分片表:

mongo --host member1.example.com:27017

mongos> sh.status();
mongos> sh.enableSharding("foo")
mongos> sh.shardCollection("foo.bar", {_id:"hashed"})
mongos> sh.status();

## 插入测试数据
use foo
for(var i = 0; i< 1000; i++){
  db.bar.insert({i:i});
}

分片扩展

​ 原来只有1个分片节点,现在要做2个。

​ 在 member2/member4/member5 上执行以下命令:

mongod –bind_ip 0.0.0.0 –replSet shard2 –dbpath /data/shard2 –logpath /data/shard2/mongod.log –port 27011 –fork –shardsvr –wiredTigerCacheSizeGB 1

​ 初始化第二个分片复制集:

mongo --host member2.example.com:27011

rs.initiate({
  _id: "shard2",
  "members":[
    {
      "_id": 0,
      "host":"member2.example.com:27011"
    },
    {
      "_id": 1,
      "host":"member4.example.com:27011"
    },
    {
      "_id": 2,
      "host":"member6.example.com:27011"
    }
  ]
});

​ 加入第2个分片,链接mongos,添加分片:

mongo --host member1.example.com:27017

mongos> sh.addShard("shard2/member2.exmple.com:27011,member4.exmple.com:27011,member6.exmple.com:27011");
mongos> sh.status();

其他阅读

​ 由于集群搭建我并没有实操,而是直接按照极客时间中的步骤进行复制粘贴。

​ 所以如果你对分片集群的搭建有什么疑惑,还可以访问这个链接:点我跳转

posted @ 2021-03-20 00:36  云崖君  阅读(218)  评论(0编辑  收藏  举报