MongoDB分片到复制集改造实践

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
简介: 背景 生产线上使用 MongoDB Sharidng 的场景非常多,但由于业务初期评估不到位或者业务发展不符合预期,为了管理起来更方便,可能需要将 Sharding 改造为 复制集。   我就针对生产级业务环境需求提供最小影响服务将分片改造为复制集(含减分片场景)的解决方案。

背景

生产线上使用 MongoDB Sharidng 的场景非常多,但由于业务初期评估不到位或者业务发展不符合预期,为了管理起来更方便,可能需要将 Sharding 改造为 复制集。


我就针对生产级业务环境需求提供最小影响服务将分片改造为复制集(含减分片场景)的解决方案。


首先,我提供两种可选方案:

1)如果有同步工具支持,可以选择从分片全量+增量的方式同步到复制集,然后选个时间点切换;

2)从集群中减分片(removeShard),最后只保留一个shard(复制集),业务接入从mongos改为复制集


当然,如果业务数据量特别少,而且可接受一定程度上的业务停服,那也可以选择逻辑导出导入的方式。尽管这种方法最为简便,但因影响服务时间过长,所以很少会在生产环境中使用。


本文,我主要讲第二种方案,其核心技术点为removeShard,但经验告诉我们,这个操作往往不会那么顺利完成,大家可能会遇到primary shard提示,也可能会遇到jumbo chunk无法迁移的问题。下面我拿一个线上正式服务的案例来详细说明。


线上案例

简单描述业务背景,起初业务评估需求特别高,因此我们采用了分片架构,设计了3个shard,通过_id进行hash分片,但后来业务远远没能达到预期目标,再后来业务越来越萎缩,到现在分片集群反而成为了业务负担。为了减少其成本,业务决定将分片替换为复制集,同时将物理机部署改为容器化。因此,我们提供了如下迁移步骤:

1)目前有三个shard,remove两个shard

2)业务从分片访问方式改为复制集访问方式

3)复制集做一次迁移,迁移到容器上


进入正题,目前我们系统有三个shard,第一步要提前确认primary shard,何为primary shard官方说明

Each database in a sharded cluster has a primary shard that holds all the un-sharded collections for that database. Each database has its own primary shard. The primary shard has no relation to the primary in a replica set.

简单理解就是没有进行分片的集合所在库的shard。那如何确认,其实也简单,笨一点办法就是连接每个分片show collection查看即可。也可以执行sh.status查看

那为什么要提前确认primary shard,因为如果是primary shard就无法remove,会有如下提示:

mongos> db.runCommand( { removeShard: "anav_team_3" } )
{
        "msg" : "draining started successfully",
        "state" : "started",
        "shard" : "anav_team_3",
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [
             "friend",
             "users"
   ],
        "ok" : 1
}

这时候如果该shard为你要删除的对象,那么需要先删除或者移动这些对象,删除不用解释,正式环境也不允许你操作,下面看下movePrimary官方文档

use admin
db.runCommand( { movePrimary: "friend", to: "anav_team_1" })
db.runCommand( { movePrimary: "users", to: "anav_team_1" })


之后,我们就可以进行removeShard了,其操作说明官方文档也非常详细。

首先保证均衡器是开启的,因为在draining数据的过程中均衡器负责将该shard上面的数据迁移至其余的shard。

mongos> sh.getBalancerState()
true
-- 如果这里结果为false,那就通过sh.setBalancerState(true)启动。
mongos> db.runCommand( { removeShard: "anav_team_2" } )
{
        "msg" : "draining started successfully",
        "state" : "started",
        "shard" : "anav_team_2",
        "note" : "you need to drop or movePrimary these databases",
        "dbsToMove" : [ ],
        "ok" : 1
}

执行完removeShard,我们再通过sh.status查看的时候可以看到指定shard正在draining数据

image.png                                           

draining数据过程非常缓慢,可以继续通过执行removeShard命令来查看当前状态:

mongos> db.runCommand( { removeShard: "anav_team_2" } )
{
    "msg" : "draining ongoing",
    "state" : "ongoing",
    "remaining" : {
        "chunks" : NumberLong(1),
        "dbs" : NumberLong(0)
    },
    "note" : "you need to drop or movePrimary these databases",
    "dbsToMove" : [ ],
    "ok" : 1
}

另外也可以通过sh.status命令看到被删除shard上的chunk数量不断减少,其余shard的chunk数量增多。

image.png                                           

mongos以及shard的日志里面也可以看到相关迁移记录。


如果业务选择了合理的片键,removeShard会顺利完成,但在我们业务中仅仅拿_id进行了hash分片,在removeShard过程中我们遇到了jumbo chunk,导致无法迁移


应对 jumbo chunk

image.png                                           

jumbo chunk如何产生呢?每个分片都会有最大chunk的大小,保存在config.settings里面:

mongos> use config
switched to db config
mongos> db.settings.findOne({"_id":"chunksize"})
{ "_id" : "chunksize", "value" : 64 }

如果片键设计不合理很容易会导致有些chunk超出上面大小,这样均衡器就无法移动这个块儿。执行sh.status(true)可以看到jumbo chunk,也可以通过查看config.chunks来获取jumbo chunk的信息:

mongos> use config
switched to db config
mongos> db.chunks.find({jumbo:true})
{ "_id" : "team.team-_id_-6148914691236517200", "lastmod" : Timestamp(19, 5), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : NumberLong("-6148914691236517200") }, "max" : { "_id" : NumberLong("-5380242722759272487") }, "shard" : "anav_team_2", "jumbo" : true }
{ "_id" : "team.team-_id_1537228672809129300", "lastmod" : Timestamp(14, 2), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : NumberLong("1537228672809129300") }, "max" : { "_id" : NumberLong("2304588625798705234") }, "shard" : "anav_team_2", "jumbo" : true }
{ "_id" : "team.team-_id_7686143364045646500", "lastmod" : Timestamp(15, 2), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : NumberLong("7686143364045646500") }, "max" : { "_id" : NumberLong("8451003142456834418") }, "shard" : "anav_team_2", "jumbo" : true }
{ "_id" : "team.team-_id_2304588625798705234", "lastmod" : Timestamp(14, 3), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : NumberLong("2304588625798705234") }, "max" : { "_id" : NumberLong("3074114379322568708") }, "shard" : "anav_team_2", "jumbo" : true }
{ "_id" : "team.team-_id_8451003142456834418", "lastmod" : Timestamp(15, 3), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : NumberLong("8451003142456834418") }, "max" : { "_id" : NumberLong("9219967741494243703") }, "shard" : "anav_team_2", "jumbo" : true }
{ "_id" : "team.team-_id_-8456336119759655766", "lastmod" : Timestamp(25, 1), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : NumberLong("-8456336119759655766") }, "max" : { "_id" : NumberLong("-7690129288411891978") }, "shard" : "anav_team_2", "jumbo" : true }
{ "_id" : "team.team-_id_-5380242722759272487", "lastmod" : Timestamp(19, 6), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : NumberLong("-5380242722759272487") }, "max" : { "_id" : NumberLong("-4612372550362685618") }, "shard" : "anav_team_2", "jumbo" : true }

从上可以看到,anav_team_2里面存在7个jumbo chunk


遇到jumbo chunk不必慌张,解决方法必然是有的。首先,我们能够想到的方法是能否直接给手动移动?官方也的确提供了moveChunk功能参考文档

db.adminCommand( { moveChunk : <namespace> ,
                 find : <query> | bounds : <array> ,
                 to : <string>,
                 _secondaryThrottle : <boolean>,
                 writeConcern: <document>,
                 _waitForDelete : <boolean> } )

提供两种方式来定位移动的对象,find后接文档查询query条件,bound则提供要移动块儿的边界,更为精准。


MongoDB不允许移动大于chunksize的chunk,所以我们可以临时将chunk大小调大,方法为:

sh.setBalancerState(false)
use config
db.settings.save({"_id":"chunksize","value":10000})
db.settings.findOne({"_id":"chunksize"})
备注 chunksize单位为M


使用moveChunk命令移动块儿到指定的shard:

mongos> use admin
switched to db admin
mongos> db.adminCommand({"moveChunk":"team.team","bounds":[{"_id":NumberLong("-6148914691236517200")},{"_id":NumberLong("-5380242722759272487")}],"to":"anav_team_1","_secondaryThrottle":true})
{
        "cause" : {
                "chunkTooBig" : true,
                "estimatedChunkSize" : 175991270,
                "ok" : 0,
                "errmsg" : "chunk too big to move"
        },
        "ok" : 0,
        "errmsg" : "move failed"
}

我这里是moveChunk失败了,原因是MongoDB 3.4版本手动moveChunk命令做了个限制。但失败归失败,如果其他版本中使用该功能时,务必注意加上_secondaryThrottle,加上会强制要求迁移过程间歇进行,每迁移完一些数据,需等待集群中大多数分片成功完成数据复制后再进入下一次迁移。尽管放慢迁移的过程,但同时减缓了对系统性能的影响。这在生产环境中还是尤为重要。当然,该选项仅仅适用于复制集shard。


移动块儿不可行我们还有一招可以尝试,那就是splitChunk,官方文档

思路就是拆分jumbo chunk为更小的块儿,然后通过均衡器来自动迁移。拿一个jumbo chunk来举例说明:

{ "_id" : "team.team-_id_MinKey", "lastmod" : Timestamp(52, 1), "lastmodEpoch" : ObjectId("59c22c0eabb58cb716f1abd7"), "ns" : "team.team", "min" : { "_id" : { "$minKey" : 1 } }, "max" : { "_id" : NumberLong("-8456336119759655766") }, "shard" : "anav_team_3", "jumbo" : true }

我们取一个min._id和max._id的大概中间值来进行split。

use admin
sh.splitAt("team.team",{"_id":NumberLong("-9000000000000000000")})

这时候我们再去查config.chunks,已经看不到该chunk信息。以此类推,其他chunk都split下,sh.status可以看到要删除shard上的chunk数量翻倍

image.png                                           

最后打开均衡器,这时候我们庆幸的发现,均衡器又开始迁移chunk了。

当然迁移过程中可能还会出现jumbo chunk,解法就是重复上面splitChunk操作


待迁移完shard上所有chunk,执行removeShard会返回成功信息。

mongos> db.runCommand( { removeShard: "anav_team_2" } )
{
      "msg" : "removeshard completed successfully",
      "state" : "completed",
      "shard" : "anav_team_2",
      "ok" : 1
}

通过该方式我成功remove了两个shard,只留下primary shard,然后通知业务服务从mongos访问改为复制集方式,后面物理机改容器这种不在本文范围内,所以不再往下去讨论。


经验教训

最后,如果是分片场景,请务必重视: 设计合理片键设计合理的片键设计合理片键念之再三,铭之肺腑。


相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
14天前
|
存储 负载均衡 NoSQL
MongoDB分片技术:实现水平扩展的利器
【4月更文挑战第30天】MongoDB的分片技术是应对数据增长和复杂业务需求的解决方案,它将数据水平拆分存储在多个实例上,实现数据库的水平扩展。分片带来水平扩展性、负载均衡、高可用性和灵活的数据管理。分片工作涉及mongos路由进程、config server和shard实例。设置分片包括部署配置服务器、添加分片、启动mongos、配置分片键和开始分片。选择合适的分片键和有效管理能确保系统性能和稳定性。
|
3天前
|
NoSQL Java MongoDB
【MongoDB 专栏】MongoDB 与 Spring Boot 的集成实践
【5月更文挑战第11天】本文介绍了如何将非关系型数据库MongoDB与Spring Boot框架集成,以实现高效灵活的数据管理。Spring Boot简化了Spring应用的构建和部署,MongoDB则以其对灵活数据结构的处理能力受到青睐。集成步骤包括:添加MongoDB依赖、配置连接信息、创建数据访问对象(DAO)以及进行数据操作。通过这种方式,开发者可以充分利用两者优势,应对各种数据需求。在实际应用中,结合微服务架构等技术,可以构建高性能、可扩展的系统。掌握MongoDB与Spring Boot集成对于提升开发效率和项目质量至关重要,未来有望在更多领域得到广泛应用。
【MongoDB 专栏】MongoDB 与 Spring Boot 的集成实践
|
3天前
|
NoSQL 算法 测试技术
【MongoDB 专栏】MongoDB 的自动分片与手动分片
【5月更文挑战第11天】MongoDB的分片技术在处理大规模数据和高并发场景中至关重要,提供自动和手动两种方式。自动分片基于预定义规则,简化管理,适合大部分场景,但灵活性有限。手动分片则允许用户自定义策略,实现高效布局,适用于有特殊需求的应用,但配置复杂。选择分片方式需考虑业务需求、数据特点和技术能力。正确实施分片策略能构建高性能、可扩展的系统,支持企业业务发展。随着技术进步,未来的分片技术将更加智能和易用。
【MongoDB 专栏】MongoDB 的自动分片与手动分片
|
3天前
|
NoSQL 安全 MongoDB
【MongoDB 专栏】MongoDB 的安全性考虑与实践
【5月更文挑战第11天】在数字化时代,MongoDB的数据安全至关重要。面临网络攻击、内部威胁、数据泄露和未授权访问等风险,我们需要重视MongoDB的安全性。关键措施包括身份验证和授权、数据加密、网络安全、备份和恢复、安全审计及正确配置。实践中应启用身份验证,配置访问控制,加密敏感数据,加强网络安全,并定期备份和审计。保持软件更新,结合业务需求制定安全策略,以确保数据的保密性、完整性和可用性。
【MongoDB 专栏】MongoDB 的安全性考虑与实践
|
4天前
|
监控 NoSQL 安全
【MongoDB 专栏】MongoDB 的复制集:高可用性配置
【5月更文挑战第10天】MongoDB的复制集是实现数据高可用性的重要机制,由主节点和次节点构成,主节点处理写操作,次节点同步数据确保一致。在主节点故障时,次节点自动提升接替,保证服务不间断。通过复制集,可实现数据保护、持续服务,适用于关键业务系统和数据备份。配置时需关注网络稳定性、节点性能和数据一致性。案例显示,复制集能有效保障服务高可用,防止数据丢失和业务中断,是现代数据库管理的关键工具。在数据驱动的世界,复制集为高可用性提供了坚实保障。
【MongoDB 专栏】MongoDB 的复制集:高可用性配置
|
4天前
|
存储 监控 NoSQL
【MongoDB 专栏】MongoDB 分片策略与最佳实践
【5月更文挑战第10天】MongoDB 分片是应对大数据量的扩展策略,涉及哈希和范围分片两种策略。分片架构包含分片服务器、配置服务器和路由服务器。最佳实践包括选择合适分片键、监控调整、避免热点数据等。注意数据分布不均和跨分片查询的挑战。通过实例展示了如何在电商场景中应用分片。文章旨在帮助理解并优化 MongoDB 分片使用。
【MongoDB 专栏】MongoDB 分片策略与最佳实践
|
14天前
|
监控 NoSQL 容灾
MongoDB复制集原理:高可用性与数据一致性的保障
【4月更文挑战第30天】MongoDB复制集提供高可用性和数据一致性,通过在多个服务器间复制数据。复制集包含主节点和从节点,写操作在主节点执行,然后异步复制到从节点。优势包括故障切换、数据冗余、负载均衡和容灾备份。当主节点故障,其他节点会选举新主节点,确保服务连续性。配置复制集涉及规划节点、配置复制集、初始化和监控维护。复制集是实现数据库可靠性的核心。
|
21天前
|
NoSQL MongoDB 数据库
|
1月前
|
NoSQL 搜索推荐 算法
【MongoDB】MongoDB在推荐系统中的实践应用
【4月更文挑战第1天】【MongoDB】MongoDB在推荐系统中的实践应用
|
2月前
|
NoSQL MongoDB
搭建MongoDB分片式集群
搭建MongoDB分片式集群
22 0