You need to enable JavaScript to run this app.
导航
MongoDB 分片集群使用指南
最近更新时间:2024.11.14 10:05:23首次发布时间:2023.12.25 11:01:19

文档数据库 MongoDB 版支持分片集群实例架构,能够提供可横向扩展的 MongoDB 服务。分片集群通过将大型集合自动分割到不同节点,来满足大规模高性能场景下的容量和性能需求。本文介绍使用 MongoDB 分片集群的相关建议供您参考。

分片集群使用场景

在如下场景中建议使用 MongoDB 分片集群:

  • 可用 RAM 或磁盘空间出现瓶颈。
  • 受单机 CPU、内存、网卡等资源限制,读写能力无法扩展。

分片使用注意事项

  • 所有需要分片的集合都必须保证分片键上存在索引,该索引可以是分片键本身的索引,也可以是复合索引。因此在设置分片键时,需注意如下几点:
    • 如果需要为已有集合(即集合中已存在数据)设置分片键,则必须先为分片键创建索引,再通过 sh.shardCollection() 命令进行分片。创建索引的具体方法,请参见 db.collection.createIndex()
    • 如果为新建集合(即集合为空)设置分片键,则直接通过 sh.shardCollection() 命令进行分片即可,系统会在分片键上自动创建索引,无需其他额外设置。
    • 您可以使用 db.collection.getIndexes() 命令查询指定集合的索引详情。
    • 若使用了复合索引,分片键必须是索引的前缀,否则在分片键时可能会出现 Uniqueness can't be maintained unless shard key is a prefix 相关的报错。
    • 当需要对已分片数据进行修改时,如果 update 请求的 filter 中未携带分片键字段并且设置了选项 upsert:true 或者multi:false,那么 update 请求会返回 An upsert on a sharded collection must contain the shard key and have the simple collation 错误。为避免影响线上业务,建议在业务低峰期执行修改。
    • 为已有集合(即集合中已存在数据)开启分片后,分片会马上进行分裂和搬迁,因此从开启分片到数据完全均衡的这段时间内,primary shard 的负载可能会增加。为避免影响线上业务,建议在业务低峰期执行分片操作。
  • 针对 MongoDB 4.0 和 4.2 版本实例,当为已有集合(即集合中已有数据)开启分片时会出现短暂的锁表禁写,在此期间写入的不带 Shard key 的 collection 将无法被 Mongos 查询,但您可以连接至具体的 Shard 进行查询。
  • 强烈建议密切关注 MongoDB 实例的表数据量,并提前为实例开启分片。

分片集群使用建议

设置合适的 Shard、Monogs 数量

分片(Shard)和分片代理(Mongos)是 MongoDB 分片集群实例中的重要组成部分。您可以根据业务场景需要,参考以下方法确定 Shard 和 Monogs 数量:

  • 分片集群仅用于解决海量数据的存储问题,且访问量不多。
    例如一个 Shard 的最大存储量为 A, 需要的存储总量是 B,那么您的业务需要的 Shard 和 Mongos 数量可以参考如下公式进行计算:
    • Shard 数量 = B ➗ A ➗ 0.75 (假设容量水位线为 75%)
    • Mongos 数量:至少部署 2 个 Mongos 做高可用
  • 分片集群用于解决高并发写入(或读取)数据的问题,但总的数据量很小,即 Shard 和 Mongos 需要满足读写性能需求。
    例如一个 Shard 的最大 QPS 为 C,一个 Mongos 的最大 QPS 为 D,业务需要的总 QPS 为 Q,那么您的业务需要的 Shard 和 Mongos 数量可以参考如下公式进行计算:
    • Shard 数量 = Q ➗ C ➗ 0.75(假设负载水位线为 75%)
    • Mongos 数量 = Q ➗ D ➗ 0.75

说明

  • 如果分片集群同时解决上述两个问题,则按照需求更高的指标进行预估。
  • 上述计算方法是基于分片集群中数据和请求都均匀分布的理想情况下进行预估,实际情况下,分布可能并不均匀,为了让系统的负载尽量均匀,您需要选择合理的 shard key。更多详情,请参见设置合适的 shard key
  • Shard 和 Mongos 支持的服务能力,需要您根据业务访问特性实测得出。

设置合适的 shard key

分片集群中数据的分片以集合为基础单位,集合中的数据通过分片键被分成多个部分。分片键是在集合中选择的一个或多个合适的字段,数据拆分时以该分片键的值为依据均衡地分布到所有分片中。如果您没有选择到合适的的分片键,可能会降低集群的使用性能,出现执行分片语句时执行过程卡住的问题。

说明

  • 从 MongoDB 5.0 起,您可以通过 reshardCollection 命令来修改分片键,实现数据的重新分配。修改分片键的具体操作步骤,请参见 Reshard a Collection
  • 从 MongoDB 4.4 起,允许分片集合中文档的分片键缺失。更多详情,请参见 Set Missing Shard Key Fields
  • 支持的分片策略
    MongoDB 分片集群支持如下两种分片策略。

    分片策略策略说明注意事项

    范围分片
    Ranged Sharding

    支持基于 shard key 的范围查询。

    当使用范围分片或哈希分片时,如下场景会影响分片效果或性能:

    • Shard key 的取值范围太小,例如将数据中心作为 shard key,由于数据中心通常不多,则分片效果不好。
    • Shard key 中某个值的文档特别多,会导致单个 chunk 特别大(即 jumbo chunk),会影响 chunk 迁移及负载均衡。
    • 根据非 shard key 进行查询、更新操作都会变成 scatter-gather 查询,影响效率。

    哈希分片
    Hashed Sharding

    能够将写入均衡分布到各个 shard。

  • 理想的 shard key 应具备的特点

    • Key 分布足够离散(sufficient cardinality)
    • 写请求均匀分布(evenly distributed write)
    • 尽量避免 scatter-gather 查询(targeted read)

    说明

    如果所选分片键不具备以上所有特点,将会影响集群的读写扩展性。例如,某字段经常被更新或经常被查询,若将其作为分片键可能会导致数据分片不均衡从而出现读写热分片,严重限制分片的优势。因此,您需要根据业务的数据分片情况、常用查询及写入的数据等场景,调整您的分片键。

  • 设置 shard key 时应考虑的因素
    您可以参考如下信息判断设置的分片键是否能够满足业务需求。

    说明

    更多详情,请参见 Choose a Shard Key

    影响因素说明

    分片键基数

    • 分片键基数决定了可以划分的数据块数量。每个唯一的分片键值在任何给定时间只能存在于单个块上。因此在条件允许的情况下,建议尽量选择基数较高的分片键。
    • 如果基数较低,意味着大部分数据可能会集中在少数几个分片上,导致分片不均衡,甚至影响数据库的性能。
      例如,使用用户 ID 作为分片键通常比使用性别作为分片键具有更高的基数,有利于数据的均匀分布。
    • 高基数或低基数的分片键本身并不能保证数据在分片集群中均匀分布,需要同时考虑分片键的出现频率和分片键值潜在的单调变化(如单调递增或单调递减),它们同样会影响数据的分布。

    分片查询模式

    • 合适的分片键可以在分片集群中均匀分布数据,同时也需要覆盖常用的查询。如果大部分查询都是基于某个字段进行的,则将该字段作为分片键可以提高查询性能。
    • 在分片集群中,如果查询包含分片键,Mongos 只会将查询路由到包含相关数据的分片。当查询不包含分片键时,查询会广播到所有分片进行评估,这会降低查询效率,也会影响集群的扩展性。
    分片键限制某些数据库可能对分片键有特定(最大长度或数据类型)的限制。在选择分片键时需要考虑这些限制。

Chunk size 与 jumbo chunk

  • Chunk size
    MongoDB 5.0 及之前版本的 chunk size 默认值为 64 MB,您可以根据业务数据量和分片使用场景修改 chunk size 值,支持的取值范围为 1MB ~ 1024 MB。修改 chunk size 值时需要注意如下几点:

    • chunk 的自动拆分操作仅发生在插入或更新数据时。
    • 较小的 chunk size 值会使数据分布更均匀,但会带来更频繁的迁移,导致查询路由开销增加。如果调小了 chunk size 值,MongoDB 会耗费一些时间将数据从原有 chunk 拆分到新 chunk,且此操作不可逆。
    • 较大的 chunk size 值通常迁移较少,查询路由和网络负载也较低,但很可能会导致数据分布不均匀,限制分片优势。如果调大了 chunk size 值,已存在的 chunk 只会等到新的插入或更新操作将其扩充至新的大小。

    更多详情,请参见 Modify Chunk Size in a Sharded Cluster

  • Jumbo chunk
    当数据持续写入 chunk 并超过 chunk size 值时,且由于某些原因无法拆分(例如一个 chunk 即为单个分片键值)时,就会被标记为 jumbo chunk。Jumbo chunk 无法被 balancer 迁移,进而可能导致负载不均衡,影响数据库性能。
    Chunk 被标记为 jumbo chunk 后,您可以参考如下方法进行处理:

    • 若该 chunk 可以被拆分,您可以尝试通过 sh.splitAt()sh.splitFind() 命令将其拆分。拆分成功后,MongoDB 会自动清除该 chunk 的 jumbo 标记。关于命令的更多详情,请参见 sh.splitAt()sh.splitFind()
    • 若该 chunk 不可再拆分,您可以尝试通过 clearJumboFlag 命令手动清除该 chunk 的 jumbo 标记。关于命令的更多详情,请参见 clearJumboFlag

      说明

      • 建议在清除前先备份 config 数据库,避免误操作导致 config 库损坏。更多详情,请参见 Clear jumbo Flag
      • 您可以尝试调大 chunk size,当 chunk 未超过 chunk size 时,jumbo 标记最后也会被清理。但随着数据的写入,jumbo chunk 仍然会出现,根本的解决办法还是需要设置合适的 shard key。更多详情,请参见设置合适的 shard key

负载均衡

MongoDB 均衡器(Balancer)会周期性的检查分片间的 chunk 数量是否存在不均衡,当各个 Shard 分片之间的 chunk 数量差异达到指定的迁移阈值时,均衡器会在分片间自动迁移数据,实现数据在分片上的均匀分布。
MongoDB 均衡器默认启用,您也可以通过 sh.stopBalancer() 命令手动关闭均衡器。更多详情,请参见 Disable the Balance
均衡器支持在线数据迁移,但数据迁移过程会对集群负载有较大影响,建议将均衡器的迁移时间设置在业务低峰期。您可以通过如下命令中的 activeWindow 参数来指定均衡器的活动时间窗口。

db.settings.updateOne(
   { _id: "balancer" },
   { $set: { activeWindow : { start : "<start-time>", stop : "<stop-time>" } } },
   { upsert: true }
)

说明

  • 请根据您的业务场景将上述代码中的 <start-time><stop-time>换成指定的时间,例如 { start : "01:30", stop : "04:30" }。更多详情,请参见 Modify the balancer's window
  • 上述时间为 MongoDB 分片集群实例所在时区的时间,当前火山引擎文档数据库 MongoDB 版所用时区均为 UTC+8。

分片集群数据分片设置实操

使用场景

某汽车自动驾驶应用需要通过 MongoDB 分片集群存储海量的车辆自动驾驶日志。假设车辆数量在百万级别,车辆每 10 秒就会向 MongoDB 上传一次日志数据,日志包含车辆 ID(VehicleId)和时间戳(Timestamp)信息,业务需要查询某辆车在某个时间内的日志信息。

Shard key 设置方案

根据上述使用场景和查询要求,可选的 shard key 设置方案见下表。

可选方案说明

方案一(推荐):
车辆 ID 和时间戳组合作为 shard key,进行范围分片。

  • 写入能均分到多个 shard。

  • 同一个车辆 ID 所对应的数据能根据时间戳进一步分散到多个 chunk。

  • 根据车辆 ID 查询时间范围的数据,能直接利用(车辆 ID,时间戳)复合索引来完成。

方案二:
时间戳作为 shard key,进行范围分片。

  • 新的写入为连续的时间戳,都会请求到同一个分片,写分布不均。

  • 根据车辆 ID 的查询会分散到所有 shard 上查询,效率低。

方案三:
时间戳作为 shard key,进行哈希分片。

  • 写入能均分到多个 shard。

  • 根据车辆 ID 的查询会分散到所有 shard 上查询,效率低。

方案四:
车辆 ID 作为 shard key,进行哈希分片。

若车辆 ID 无明显规则,也可进行范围分片。

  • 写入能均分到多个 shard。

  • 同一个车辆 ID 所对应的数据无法进一步细分,只能分散到同一个 chunk,会造成 jumbo chunk。

  • 根据车辆 ID 的查询只请求到单个 shard,请求路由到单个 shard 后,根据时间戳的范围查询需要全表扫描并排序。

Shard key 设置步骤

本文根据上述使用场景和查询请求要求,以推荐的方案一(即组合车辆 ID 和时间戳作为 shard key,进行范围分片)为例,详细介绍 MongoDB 分片集群 shard key 的设置步骤。

说明

下述示例中以数据库 IoV,集合 AutonomousCar,字段 VehicleIdTimestamp 为例介绍相关操作步骤。

  1. 通过 Mongo Shell 工具登录目标分片集群实例。具体操作步骤,请参见通过 Mongo Shell 工具连接实例

  2. 执行 use <数据库名称> 命令进入目标数据库。
    示例如下。

    use IoV
    
  3. 执行如下命令判断目标集合是否已分片。

    db.<collection>.getShardDistribution()
    

    若返回结果为 Collection <collection> is not sharded 表示当前集合未分片。更多详情,请参见 db.collection.getShardDistribution()
    命令示例如下:

    db.AutonomousCar.getShardDistribution()
    
  4. 对集合所属的数据库启用分片功能。
    您可以选择如下任意一种方法启用目标数据库的分片功能。

    • 方法一:
      通过 Mongo Shell 工具登录目标分片集群实例后,直接执行如下命令:
      sh.enableSharding("<database>")
      
      命令示例如下。
      sh.enableSharding("IoV")
      
    • 方法二:
      通过 Mongo Shell 工具登录目标分片集群实例后,先执行 use admin 命令进入 admin 数据库,然后执行如下命令:
      db.runCommand({enablesharding:"<database>"})
      
      命令示例如下。
      db.runCommand({enablesharding:"IoV"})
      
  5. 确认 MongoDB 均衡器(Balancer)是否已开启,命令如下。

    sh.getBalancerState()
    

    若返回结果为 true 表示均衡器已启用,当各个 Shard 分片之间的 chunk 数量差异达到指定的迁移阈值时,均衡器会在分片间自动迁移数据,实现数据在分片上的均匀分布。更多详情,请参见 Check the Balancer State
    若返回结果为 false 表示均衡器未启用,您可以通过 sh.startBalancer() 命令开启均衡器。更多详情,请参见 Enable the Balancer

    说明

    从 MongoDB 4.2起,通过 sh.startBalancer() 命令开启均衡器时,会默认打开分片集群的自动分裂功能,更多详情,请参见 sh.enableAutoSplit

  6. 对集合进行分片。
    您可以选择如下任意一种方法对目标集合进行分片。

    说明

    本文示例中使用了 VehicleIdTimestamp 字段组合作为 shard key,您需要先为这两个字段创建复合索引。创建索引的具体方法,请参见 db.collection.createIndex()

    • 方法一:
      通过 Mongo Shell 工具登录目标分片集群实例后,直接执行如下命令。

      说明

      关于 sh.shardCollection 命令的更多详情,请参见 sh.shardCollection()

      sh.shardCollection("<database>.<collection>",{"<keyname>":<value> })
      
      命令示例如下。
      sh.shardCollection("IoV.AutonomousCar",{"VehicleId":1,"Timestamp":1})
      
    • 方法二:
      通过 Mongo Shell 工具登录目标分片集群实例后,先执行 use admin 命令进入 admin 数据库,然后执行如下命令。

      说明

      关于 shardcollection 命令的更多详情,请参见 shardCollection

      db.runCommand({shardcollection:"<database>.<collection>",key:{"keyname":<value> }})
      
      命令示例如下。
      db.runCommand({shardcollection:"IoV.AutonomousCar",key:{"VehicleId":1,"Timestamp":1}})
      
    参数说明
    <database>目标数据库的名称。
    <collection>目标集合的名称。
    <keyname>分片键。集群实例将根据该值进行数据分片,请结合实际业务为集合选择合适的分片键。更多详情,请参见设置合适的 shard key

    <value>

    基于分片键的分片策略。取值范围如下:

    • 1:表示使用范围分片。
    • hashed:表示使用 Hash 分片。

    说明

    关于分片策略的更多信息,请参见支持的分片策略

    numInitialChunks当使用Hash分片键对空集合进行分片时,指定初始创建的最小分片数。关于 numInitialChunks 的更多信息,请参见 sh.shardCollection()_optionshardCollection_command-fields
  7. 在目标数据库中,执行如下命令查看数据库在各分片节点的数据存储情况。

    sh.status()
    

    更多详情,请参见 sh.status()