认识 MongoDB 的分片集群

发表于 2023-02-23

分片集群架构

架构图

分片集群架构图

集群角色

  • Config
    • 配置集群
    • 在配置集群中记录数据具体落在哪个分片上
  • Mongos
    • 路由器集群
    • 由路由器集群对外提供服务,数据具体怎么分片不需要应用程序关心
  • Shard
    • 分片集群
    • 具体存储数据的分片

分片概念

  • 片键
    • 用于计算当前数据落在哪个分片关键字段
    • 是 Document 的一个或多个字段
  • 文档(Document)
    • 一条数据
  • 数据逻辑块(Chunk)
    • 将数据据根据分片字段的基数分成多个逻辑块
      • 例如:年龄范围为 0 ~ 100,按年龄就分成 101 个 Chunk
    • 分片集群中最小的数据存储(落盘)单位是 Chunk
  • 分片
    • 分片是一个分片集群
    • 一个分片中包含多个数据逻辑块
    • 当某个分片数据量过大时,集群会自动均衡分片之间的数据量
    • 这个时候就是移动 Chunk 的方式,均衡数据
      • 例如:
        • 分片 1 上有 100 个 Chunk
        • 分片 2 上有 20 个 Chunk
        • 集群均衡两个分片上的数据量,将分片 1 的 Chunk 移动 40 个到分片 2
        • 最后分片 1、2 上各有 60 个 Chunk
  • 集群
    • 多个分片构成的一整个 MongoDB 服务

数据分布方式

  • 基于范围
    • 片范围查询性能好,从某个分片即可完成查询
    • 数据分布不均匀,容易有热点
      • 例如自增主键,写都落在某一个分片
  • 基于 Hash
    • 数据分布均匀,写操作均匀落在各个分片上
    • 数据随机分散,读取时需要各个分片响应才能返回数据
    • 适用写多读少的场景
  • 基于 zone/tag
    • 使用区域或者标签将数据分到指定分片
      • 例如北京的数据落在北京的分片集群,供给北京用户使用。各个城市有自己的集群
    • 适用跨地域多中心的场景

分片设计

合理的分片大小

  • 数据量
    • 单个分片不超过 3T,尽可能保持在 2T
  • 索引
    • 常用索引必须容纳进内存

分片数量

  • 存储
    • 按每个分片 2T 的容量计算:总容量 / 2T = 预计分片数量
  • 内存
    • 需要放在内存中的数据:工作集(热数据 + 索引)
    • 按公式计算:工作集大小 / (单服务器内存容量 * 0.6) = 预计分片数量
      • 服务器内存需要折算一部分预留内存,系数预计 0.6
  • 并发量
    • 按公式计算:并发总数 / (单服务器并发 * 0.7) = 预计分片数量
      • 单服务器并发需要这算一部分集群消耗,系数预计 0.7
  • 最后取预估存储、内存、并发量计算出来的分片数量的最大值

片键选择

  • 小基数片键
    • 小基数片键导致 Chunk 数量有限
    • 随着数据量越来越多可导致单个 Chunk 巨大
    • 巨大的 Chunk 水平扩展困难
    • 因此片键需要选择大基数的
  • 数据分布不均匀的片键
    • 因为数据分布不均匀,很多数据落在某几个 Chunk
    • 访问压力就会落在数据量大的 Chunk 上
    • 同样巨大的 Chunk 会导致水平扩展困难
    • 例如:
      • 年龄 0 ~ 100
      • 使用系统的人年龄范围在 15 ~ 18
      • 虽然有 101 个 Chunk,实际有效的只有 4 个
  • 定向性
    • 如果片键作为查询条件,可以直接定位到分片
    • 可以避免询问完所有分片再返回数据
  • 总结
    • 片键需要有足够大的基数
    • 片键需要数据分布均匀

分片集群的搭建

准备复制集群

集群名 replicate,构建分片集群时需要用到 集群名 shard,构建分片集群时需要用到

# 启动分片节点
# --shardsvr 必须指定,指定了才能被加入分片
# --replSet 指定复制集群名
mongod --bind_ip 0.0.0.0 --replSet replicate --dbpath /data/db --logpath /var/log/mongo/mongod.log --port 27017 --fork --shardsvr
# 构建 config 集群
rs.initiate({
    "_id": "replicate",
    "members": [
        {
            "_id": 0,
            "host": "10.0.0.2:27017"
        },
        {
            "_id": 1,
            "host": "10.0.0.3:27017"
        },
        {
            "_id": 2,
            "host": "10.0.0.4:27017"
        }
    ]
})
# 启动分片节点
# --shardsvr 必须指定,指定了才能被加入分片
# --replSet 指定复制集群名,区分其他复制集群
mongod --bind_ip 0.0.0.0 --replSet shard --dbpath /data/db --logpath /var/log/mongo/mongod.log --port 27017 --fork --shardsvr
# 构建 config 集群
rs.initiate({
    "_id": "shard",
    "members": [
        {
            "_id": 0,
            "host": "10.0.0.5:27017"
        },
        {
            "_id": 1,
            "host": "10.0.0.6:27017"
        },
        {
            "_id": 2,
            "host": "10.0.0.7:27017"
        }
    ]
})

准备配置集群

# 启动 config 节点
# --configsvr 必须指定
# --replSet 指定复制集群名,区分其他复制集群
mongod --bind_ip 0.0.0.0 --replSet config --dbpath /data/db --logpath /var/log/mongo/mongod.log --port 27017 --fork --configsvr
# 构建 config 集群
rs.initiate({
    "_id": "config",
    "members": [
        {
            "_id": 0,
            "host": "10.0.0.8:27017"
        },
        {
            "_id": 1,
            "host": "10.0.0.9:27017"
        },
        {
            "_id": 2,
            "host": "10.0.0.10:27017"
        }
    ]
})

准备路由集群

# 启动 mongos 节点
# --configdb 指向配置集群
#     集群URI:<replicate_name>/host:port,host:port,host:port
#         config/10.0.0.8:27017,10.0.0.9:27017,10.0.0.10:27017
mongos --bind_ip 0.0.0.0 --replSet mongos --logpath /var/log/mongo/mongod.log --port 27017 --fork --configdb config/10.0.0.8:27017,10.0.0.9:27017,10.0.0.10:27017
# 构建 mongos 集群(测验时未构建集群,原理一致)
rs.initiate({
    "_id": "mongos",
    "members": [
        {
            "_id": 0,
            "host": "10.0.0.11:27017"
        },
        {
            "_id": 1,
            "host": "10.0.0.12:27017"
        },
        {
            "_id": 2,
            "host": "10.0.0.13:27017"
        }
    ]
})

构建分片集群

  • 命令登录 mongos 主节点
# 添加分片
sh.addShard("replicate/10.0.0.2:27017,10.0.0.3:27017,10.0.0.4:27017")
sh.addShard("shard/10.0.0.5:27017,10.0.0.6:27017,10.0.0.7:27017")
# 启用分片
sh.enableSharding("demo")
# 指定分片集合,并指定片键
sh.shardCollection("demo.order", {"_id": "hashed"})
# 查看分片集群状态
sh.status()