在生产环境里部署分片集群时,面前会出现很多选择和挑战。这里我会描述几个推荐的部署拓扑,针对常见的部署问题做出解答。我们随后还会考虑一些服务器管理方面的问题,包括监控、备份、故障转移和恢复。
9.5.1 部署与配置
一开始很难搞定分片集群的部署与配置,下文是一份指南,介绍了轻松组织并配置集群的方法。
1. 部署拓扑
要运行示例MongoDB分片集群,你一共要启动九个进程(每个副本集三个mongod
,外加三个配置服务器)。乍一看,这个数字有点吓人。一开始用户会假设在生产环境里运行两个分片的集群要有九台独立的机器。幸运的是,实际需要的机器要少很多,看一下集群中各组件所要求的资源就能知道为什么了。
首先考虑一下副本集,每个成员都包含分片的完整数据副本,可能是主节点,也可能是从节点。这些进程总是要求有足够的磁盘空间来保存数据,要有足够的内存高效地提供服务。因此,复制mongod
是分片集群中最资源密集型的进程,必须占用独立的机器。
那副本集的仲裁节点呢?这些进程只保存副本集的配置数据,这些数据就放在一个文档里。所以,仲裁节点开销很少,当然也就不需要自己的服务器了。
接下来是配置服务器,它们同样只保存相对较少的数据。举例来说,配置服务器上管理示例副本集的数据一共也就大约30 KB。如果假设这些数据会随着分片集群数据的增长而线性增长,那么1 TB的分片集群可能仅会对应30 MB数据。1也就是说配置服务器同样不需要有自己的机器。但是,考虑到配置服务器所扮演的重要角色,一些用户更倾向于为它们提供一些机器(或虚拟机)。
1. 这是一个相当保守的估计,真实值可能会小得多。
根据你对副本集和分片集群的了解,可以列出部署分片集群的最低要求。
副本集的每个成员,无论是完整的副本节点还是仲裁节点,都需要放在不同的机器上。
每个用于复制的副本集成员都需要有自己的机器。
副本集的仲裁节点是很轻量级的,和其他进程共用一台机器就可以了。
配置服务器也可以选择与其他进程共用一台机器。唯一的硬性要求是配置集群中的所有配置服务器都必须放在不同的机器上。
你可能感觉要满足这些规则会引起逻辑问题。我们将运用这些规则:针对示例的两分片集群,你会看到两个合理的部署拓扑。第一个拓扑只需要四台机器,图9-4里描绘了进程的分布情况。
图9-4 部署在四台机器上的两分片集群
这个配置满足了刚才所说的所有规则。在每台机器上占主导地位的是各分片的复制节点。剩下的进程经过了精心安排,所有的三个配置服务器和每个副本集的仲裁节点都部署在了不同的机器上。说起容错性,该拓扑能容忍任何一台机器发生故障。无论哪台机器发生了故障,集群都能继续处理读写请求。如果发生故障的机器正好运行了一个配置服务器,那么所有的块拆分和迁移都会暂停。2幸运的是,暂停分片操作基本不会影响分片集群的工作;在损失的机器恢复后,就能进行拆分和迁移了。
2. 在发生任何分片操作时,所有的三台配置服务器都必须在线。
这是两分片集群的最小推荐配置。但是,那些要求最高可用性和最快恢复途径的应用程序需要一些更强健的东西。正如上一章里讨论的那样,包含两个副本和一个仲裁节点的副本集在恢复时是很脆弱的。如果有三个节点,就能降低恢复时的脆弱程度,还能让你在从数据中心里部署一个节点,用于灾难恢复。图9-5是一个强壮的两分片集群拓扑。每个分片都包含三节点的副本集,每个节点都包含数据的完整副本。为了进行灾难恢复,从每个分片里抽一个节点,加上一个配置服务器,部署在从数据中心;要保证那些节点不会变成主节点,可以将它们的优先级设置为0。
图9-5 部署在两个数据中心、六台机器上的两分片集群
用了这个配置,每个分片都会被复制两次,而非仅一次。此外,当主数据中心发生故障时,从数据中心拥有重建分片集群所需的全部数据。
数据中心故障
最有可能发生的数据中心故障是电力中断。在没有开启Journaling日志的情况下运行MongoDB服务器时,电力中断就意味着非正常关闭MongoDB服务器,可能会损坏数据文件。发生这种故障时,唯一可靠的恢复途径是数据库修复,一个保证停机时间的漫长过程。
大多数用户只将整个集群部署在一个数据中心里,这对大量应用程序来说都没问题。这种情况下的主要预防措施是,至少在每个分片的一个节点以及一台配置服务器上开启Journaling日志。在电力供应恢复时,这能极大地提高恢复速度。第10章里会涉及Journaling日志的相关内容。
尽管如此,一些故障更加严重。电力中断有时能持续几天。洪水、地震,以及其他自然灾害能完全摧毁数据中心。对于那些想在此类故障中进行快速恢复的用户而言,他们必须跨多个数据中心部署分片集群。
哪种分片拓扑最适合你的应用程序,这种决策总是基于一系列与你能容忍的停机时间有关的考虑,比如根据MTR(Mean Time to Recovery,平均恢复时间)进行评估。考虑潜在的故障场景,并模拟它们。如果一个数据中心发生故障,考虑一下它对应用程序(或业务)的影响。
2. 配置注意事项
下面是一些与配置分片集群相关的注意事项。
- 估计集群大小
用户经常想知道要部署多少个分片,每个分片应该有多大。当然,这个问题的答案取决于所在的环境。如果是部署在亚马逊的EC2上,在超过最大的可用实例前都不应该进行分片。在本书编写时,最大的EC2节点有68 GB内存。如果运行在自己的硬件上,你还可以拥有更大的机器。在数据量达到100 GB之前都不进行分片,这是很合理的。
当然,每增加一个分片都会引入额外的复杂性,每个分片都要求进行复制。所以说,少数大分片比大量小分片要好。
- 对现有集合进行分片
你可以对现有集合进行分片,如果花了很多时间才将数据分布到各分片里,请不要大惊小怪的。每次只能做一轮均衡,迁移过程中每分钟只能移动大约100~200 MB数据。因此,对一个50 GB的集合进行分片大约需要八个小时,其中还可能牵涉一定的磁盘活动。此外,在对这样的大集合进行初始分片时,可能还要手动拆分以加速分片过程,因为拆分是由插入触发的。
说到这里,应该已经很清楚了,在最后时刻对一个集合进行分片并不是处理性能问题的好办法。如果你计划在未来某个时刻对集合进行分片,考虑到可以预见的性能下降,应该提前进行分片。
- 在初始加载时预拆分块
如果你有一个很大的数据集需要加载到分片集合里,并且知道数据分布的规律,那么可以通过对块的预拆分和预迁移节省很多时间。举个例子,假设你想要把电子表格导入到一个新的MongoDB分片集群里。可以在导入时先拆分块,随后将它们迁移到分片里,借此保证数据是均匀分布的。你能用split
和moveChunk
命令实现这个目标,它们的辅助方法分别是sh.splitAt
和sh.moveChunks
。
下面是一个手动块拆分的例子。你发出split命令,指定你想要的集合,随后指明拆分点:
> sh.splitAt( "cloud-docs.spreadsheets",{ "username" : "Chen", "_id" : ObjectId("4d6d59db1d41c8536f001453") })
命令运行时会定位到某个块,而这个块逻辑上包含username
是Chen
并且_id
是ObjectId ("4d6d59db1d41c8536f001453")
的文档3。该命令随后会根据这个点来拆分块,最后得到两个块。你能像这样继续拆分,直到拥有数据良好分布的块集合。你还要确保创建足够数量的块,让平均块大小保持在64 MB的拆分阈值以内。所以,如果想加载1GB数据,应该计划创建大约20个块。
3. 注意,并不需要存在这样一个文档。事实上,你正在对一个空集合做拆分。
第二步是确定所有分片都拥有数量相当的块。因为所有的块最初都在一个分片上,你需要移动它们。可以使用moveChunk
命令来移动块。辅助方法能简化这个过程:
> sh.moveChunk("cloud-docs.spreadsheets", {username: "Chen"}, "shardB")
这句语句的意思是把逻辑上包含文档{username: "Chen"}
的块移动到分片B上。
9.5.2 管理
我将简单介绍一些分片管理的知识,让本章内容更充实一些。
1. 监控
分片集群是整个体系中比较复杂的一块,正因此,你应该严密监控它。在任何mongos
上都可以运行serverStatus
和currentOp
命令,命令的输出能反映所有分片的聚合统计信息。在下一章里我将更具体地讨论这些命令。
除了聚合服务器的统计信息,你还希望能监控块的分布和各个块的大小。正如在示例集群中看到的那样,所有的信息都保存在config
数据库里。如果发现不平衡的块或者未经确认的块增长,可以通过split
和moveChunk
命令处理这些情况。或者,也可以查看日志,检查均衡操作是否出于某些原因被停止了。
2. 手动分区
有一些情况下,你可能希望手动对线上分片集群的块进行拆分和迁移。例如,自MongoDB v2.0起,均衡器并不会直接考虑某个分片的负载。很明显,一个分片的写越多,它的块就越大,最终就会造成迁移。但是,不难想象你可以通过迁移块来减轻分片的负载。moveChunk
命令在这种情况下同样很有帮助。
3. 增加一个分片
如果你决定要增加更多容量,可以使用与先前一样的方法向现有集群添加新的分片:
sh.addShard("shard-c/rs1.example.net:27017,rs2.example.net:27017")
使用这种方式增加容量时,要注意向新分片迁移数据所花费的时间。如前所述,预计的迁移速度是每分钟100~200 MB。这意味着如果需要向分片集群增加容量,你应该早在性能下降以前就开始行动。要决定何时需要添加新分片,考虑一下数据集的增长速率。很明显,你希望将索引和工作集保持在内存里。因此,最好在索引和工作集达到现有分片内存90%之前的几个星期就开始计划添加新分片。
如果你不愿意采用此处描述的安全途径,那么就会将自己置身于痛苦之中。一旦内存里容纳不下索引和工作集,应用程序就会中止运行,尤其是那些要求很高读写吞吐量的应用程序。问题在于数据库需要在磁盘和内存之间置换分页,这会降低读写速度,后台日志操作无法放入读写队列。从这点来看,增加容量是件困难的事,因为分片之间的块迁移会增加现有分片的读负载。很明显,在数据库已经超载之时,你最后想做的还是增加负载。
说了这么多,只是为了强调你应该监控集群,在真正有需要之前就增加容量。
4. 删除分片
在一些很少见的情况下,你可能会想删除一个分片。可以通过removeshard
命令进行删除:
> use admin> db.runCommand({removeshard: "shard-1/arete:30100,arete:30101"}){ "msg" : "draining started successfully", "state" : "started", "shard" : "shard-1-test-rs", "ok" : 1 }
命令的响应说明正在从分片中移除块,它们将被重新分配到其他分片上。可以再次运行该命令来检查删除过程的状态:
> db.runCommand({removeshard: "shard-1/arete:30100,arete:30101"}){ "msg" : "draining ongoing", "state" : "ongoing", "remaining" : { "chunks" : 376, "dbs" : 3 }, "ok" : 1 }
一旦分片被清空,你还要确认将要删除的分片不是数据库的主分片。可以通过查询config.databases
集合的分片成员进行检查:
> use config> db.databases.find { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "cloud-docs", "partitioned" : true, "primary" : "shardA" } { "_id" : "test", "partitioned" : false, "primary" : "shardB" }
从中可以看到,cloud-docs
数据库属于shardA
,而test
数据库则属于shardB
。因为正在删除shardB
,所以需要改变test
数据库的主节点。为此,可以使用moveprimary
命令:
> db.runCommand({moveprimary: "test", to: "shard-0-test-rs" });
对于每个主节点是将要删除的分片的数据库,运行该命令。随后,再次对每个已清空的分片运行removeshard
命令:
> db.runCommand({removeshard: "shard-1/arete:30100,arete:30101"}){ "msg": "remove shard completed successfully", "stage": "completed", "host": "arete:30100", "ok" : 1}
一旦看到删除完成,就可以安全地将已删除的分片下线了。
5. 集合去分片
虽然可以删除一个分片,但是没有正式的途径去掉集合的分片。如果真的需要这么做,最好的选择是导出集合,再用一个不同的名字将数据恢复到一个新的集合里。4然后就能把已经导出数据的分片集合删掉了。例如,假设foo
是一个分片集合,你必须用mongodump
连接mongos
来导出foo
集合的数据:
4. 下一章将涉及用来进行导出和恢复的工具——mongodump
和mongorestore
。
$ mongodump -h arete --port 40000 -d cloud-docs -c fooconnected to: arete:40000DATABASE: cloud-docs to dump/cloud-docs cloud-docs.foo to dump/cloud-docs/foo.bson 100 objects
该命令能把该集合导出到一个名为foo.bson
的文件里,随后再用mongorestore
来恢复该文件:
$ mongorestore -h arete --port 40000 -d cloud-docs -c barTue Mar 22 12:06:12 dump/cloud-docs/foo.bsonTue Mar 22 12:06:12 going into namespace [cloud-docs.bar]Tue Mar 22 12:06:12 100 objects found
将数据移动到未分片集合之后,就可以随意删除旧的分片集合foo
了。
6. 备份分片集群
要备份分片集群,你需要配置数据以及每个分片数据的副本。有两种途径来获得这些数据。第一种是使用mongodump
工具,从一个配置服务器导出数据,随后再从每个单独的分片里导出数据。此外,也可以通过mongos
路由器运行mongodump
,一次性导出整个分片集合的数据,包括配置数据库。这种策略的主要问题是分片集合的总数据可能太大了,以至于无法导出到一台机器上。
另一种常用的备份分片集群的方法是从每个分片的一个成员里复制数据文件,再从一台配置服务器中复制数据文件。下一章里会介绍这种备份独立mongod
进程和副本集的方法。你只要在每个分片和一台配置服务器上执行这个过程就可以了。
无论选择哪种备份方式,都需要确认在备份系统时没有块处在移动过程中。也就是说要停止均衡器进程。
- 停止均衡器
到目前为止,禁用均衡器就是upsert一个文档到config
数据库的settings
集合:
> use config> db.settings.update({_id: "balancer"}, {$set: {stopped: true}}, true);
这里一定要小心:更新了配置之后,均衡器可能仍在工作。在备份集群之前,你还需要再次确认均衡器完成了最后一轮均衡。最好的方法就是检查locks
集合,找到_id
是balancer
的条目,确认它的状态是0。下面是一个例子:
> use config> db.locks.find({_id: "balancer"}){ "_id" : "balancer", "process" : "arete:40000:1299516887:1804289383", "state" : 1, "ts" : ObjectId("4d890d30bd9f205b29eda79e"), "when" : ISODate("2011-03-22T20:57:20.249Z"), "who" : "arete:40000:1299516887:1804289383:Balancer:846930886", "why" : "doing balance round"}
任何大于0的状态值都说明均衡仍在进行中。process
字段显示了负责组织协调均衡的mongos
所运行在的计算机的主机名和端口,本例中,主机是arete:40000
。如果在修改配置之后,均衡器始终没有停止,你应该检查负责均衡的mongos
的日志,查找错误。
在均衡器停止之后,就可以安全地开始备份了。备份完成后,不要忘了重新启动均衡器。为此,可以重新设置stopped
的值:
> use config> db.settings.update({_id: "balancer"}, {$set: {stopped: false}}, true);
为了简化与均衡器相关的一些操作,MongoDB v2.0引入了一些Shell辅助方法。例如,可以用sh.setBalancerState
来启动和停止均衡器:
> sh.setBalancerState(false)
这相当于调整settings
集合中的stopped
值。用这种方式禁用均衡器之后,可以不停地调用sh.isBalancerRunning
,直到均衡器停下为止。
7. 故障转移与恢复
虽然我们已经讲过了一般的副本集故障,但还是有必要提一下分片集群的潜在故障点和恢复的最佳实践。
- 分片成员故障
每个分片都由一个副本集组成。因此,如果这些副本集中的任一成员发生故障,从节点就会被选举为主节点,mongos
进程会自动连接到该节点上。第8章描述了恢复副本集故障成员的具体步骤。选择哪种方法依赖于成员是何故障,但是不管怎么样,恢复的指南都是一样的,无论副本集是否是分片集群的组成部分。
如果发现副本集在故障转移之后有什么不正常的表现,可以通过重启所有mongos
进程重置系统,这能保证适当连接都指向新的副本集。此外,如果发现均衡器不工作了,就检查config
数据库的locks
集合,找到process
字段指向之前主节点的条目。如果有这样的条目,锁文档已经旧了,你可以安全地手动删除该文档。
- 配置服务器故障
一个分片集群要有三台配置服务器才能正常运作,其中最多能有两台发生故障。无论何时,当配置服务器数量少于三台,剩余的配置服务器会变为只读状态,所有的拆分和均衡操作都会停止。请注意,这对整个集群没有负面影响,集群的读写仍能正常进行,当所有三台配置服务器都恢复之后,均衡器将从它停止的地方重新开始工作。
要恢复配置服务器,从现有的配置服务器把数据文件复制到发生故障的机器上,随后重启服务器。4
4. 和往常一样,在复制任何数据文件之前,确保已经锁定了mongod
(第10章会做描述)或者正常关闭了该进程。不要在服务器仍在运行时复制任何数据文件。
mongos
故障
要是mongos
进程发生故障,没有什么好担心的。如果mongos
运行在应用服务器上,它发生故障了,那么很有可能你的应用程序服务器也发生故障了。这时的恢复就是简单地恢复服务器。
无论mongos
出于什么原因发生故障,进程本身都没有自己的状态。这意味着恢复mongos
就是简单地重启进程,在配置服务器上指向它而已。