MongoDB分片集群搭建
在如今的互联网环境下,海量数据已随处可见并且还在不断增长,对于如何存储处理海量数据,比较常见的方法有两种:
- 垂直扩展:通过增加单台服务器的配置,例如使用更强悍的 CPU、更大的内存、更大容量的磁盘,此种方法虽然成本很高,但是实现比较简单,维护起来也比较方便。
- 水平扩展:通过使用更多配置一般的服务器来共同承担工作负载,此种方法很灵活,可以根据工作负载的大小动态增减服务器的数量,但是实现比较复杂,得有专门的人员来运维。
MongoDB 支持通过分片技术从而进行水平扩展,用以支撑海量数据集和高吞吐量的操作。如果数据集不够大,还是建议您使用 MongoDB 副本集,因为分片需要处理更多的技术细节,所以在分片环境下其性能可能始终没有副本集性能强。本文通过介绍如何搭建 MongoDB 分片集群以及及一些相关核心概念,可以帮您快速理解 MongoDB 是如何通过分片技术来处理海量数据的。
MongoDB分片集群搭建
分片集群组件
MongoDB 分片集群(参考官方文档 Sharded Cluster)由以下三个组件构成,缺一不可:
- mongos:数据库集群请求的入口,所有的请求都通过 mongos 进行协调,不需要在应用程序添加一个路由选择器,mongos 自己就是一个请求分发中心,它负责把对应的数据请求请求转发到对应的 shard 服务器上。在生产环境通常有多 mongos 作为请求的入口,防止其中一个挂掉所有的 mongodb 请求都没有办法操作。
- config server:顾名思义为配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos 本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos 第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。从 MongoDB 3.4 开始,必须将配置服务器部署为副本集(CSRS,全称是 Config Servers Replica Set),因为它存储了分片路由的元数据,防止数据丢失!
- shard,分片(sharding)是指将数据库拆分,将其分散在不同的机器上的过程,每个分片是整体数据的子集,且都可以部署为副本集。将数据分散到不同的机器上,不需要功能强大的服务器就可以存储更多的数据和处理更大的负载。基本思想就是将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分,最后通过一个均衡器来对各个分片进行均衡(数据迁移)。
其它名词概念:
- replica set:中文翻译副本集,其实就是 shard 的备份,防止 shard 挂掉之后数据丢失。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。
- 仲裁者(Arbiter):是复制集中的一个 MongoDB 实例,它并不保存数据。仲裁节点使用最小的资源并且不要求硬件设备,不能将 Arbiter 部署在同一个数据集节点中,可以部署在其他应用服务器或者监视服务器中,也可部署在单独的虚拟机中。为了确保复制集中有奇数的投票成员(包括 primary),需要添加仲裁节点做为投票,否则 primary 不能运行时不会自动切换 primary。
简单了解之后,我们可以这样总结一下,应用请求 mongos 来操作 mongodb 的增删改查,配置服务器存储数据库元信息,并且和 mongos 做同步,数据最终存入在 shard(分片)上,为了防止数据丢失同步在副本集中存储了一份,仲裁在数据存储到分片的时候决定存储到哪个节点。
Sharded Key 分片键
MongoDB 通过定义 shared key(分片键)从而对整个集合进行分片,分片键的好坏直接影响到整个集群的性能。另外需要注意的是,一个集合只有且只能有一个分片键,一旦分片键确定好之后就不能更改。分片键分为以下两种类型:
- 基于 Hashed 的分片:MongoDB 会计算分片键字段值的哈希值,用以确定该文档存于哪个 chunk,从而达到将集合分摊到不同的 chunk。此种类型能够使得数据整体分布比较均匀,对于等值查询效率很高,但是对于范围查询效率就比较低,因为可能要扫描所有的分片才能获取到数据。
- 基于 Ranged 的分片:MongoDB 会将相似的值放到一个 chunk 中,所以说如果在查询的时候带上分片键的范围条件,查询效率会非常高,因为不需要扫描所有的分片就可以定位到数据。注意,如果片键的值为单调递增或单调递减,那么不适合采用该分片策略,因为数据总会写到一个分片,从而没有很好地分散 IO。
分片键的类型需要根据实际的业务场景决定,例如有张非常大的用户表,用户表里有用户 ID 字段,每次查询的时候都会带上用户 ID,如果想对该用户表进行分片,可以选择将用户 ID 字段作为 shard key,并且分片键类型可以使用基于 Hashed 的分片。
Chunk 块
chunk(块)是均衡器迁移数据的最小单元,默认大小为 64MB,取值范围为 1-1024MB。一个块只存在于一个分片,每个块由片键特定范围内的文档组成,块的范围为左闭又开即 [start,end)
。一个文档属于且只属于一个块,当一个块增加到特定大小的时候,会通过拆分点(split point)被拆分成 2 个较小的块。在有些情况下,chunk 会持续增长,超过 ChunkSize,官方称为 jumbo chunk,该块无法被 MongoDB 拆分,也不能被均衡器迁移,故久而久之会导致 chunk 在分片服务器上分布不均匀,从而成为性能瓶颈,表现之一为 insert 数据变慢。
Chunk 的拆分
mongos 会记录每个块中有多少数据,一旦达到了阈值就会检查是否需要对其进行拆分,如果确实需要拆分则可以在配置服务器上更新这个块的相关元信息。
chunk 的拆分过程如下:
- mongos 接收到客户端发起的写请求后会检查当前块的拆分阈值点。
- 如果需要拆分,mongos 则会像分片服务器发起一个拆分请求。
- 分片服务器会做拆分工作,然后将信息返回 mongos。
注意,相同的片键只能保存在相同的块中,如果一个相同的片键过多,则会导致一个块过大,成为 jumbo chunk,所以具有不同值的片键很重要。
Chunk 的迁移过程
- 均衡器进程发送
moveChunk
命令到源分片。 - 源分片使用内部
moveChunk
命令,在迁移过程,对该块的操作还是会路由到源分片。 - 目标分片构建索引。
- 目标分片开始进行数据复制。
- 复制完成后会同步在迁移过程中该块的更改。
- 同步完成后源分片会连接到配置服务器,使用块的新位置更新集群元数据。
- 源分片完成元数据更新后,一旦块上没有打开的游标,源分片将删除其文档副本。
迁移过程可确保一致性,并在平衡期间最大化块的可用性。
Chunk 迁移的阈值
为了最大程度地减少平衡对集群的影响,平衡器仅在分片集合的块分配达到某些阈值之后才开始平衡。阈值适用于集合中具有最多块的 shard 与该集合中具有最少块的 shard 之间的块数差异。平衡器具有以下阈值:
Number of Chunks | Migration Threshold |
---|---|
Fewer than 20 | 2 |
20-79 | 4 |
80 and greater | 8 |
当该集合的任何两个分片上的块数之差小于2或块迁移失败时,平衡器将停止在目标集合上运行。
修改 Chunk Size 的注意事项
修改 chunk 大小需要注意以下几点:
- chunk 的自动拆分操作仅发生在插入或更新的时候。
- 如果减少 chunk size,将会耗费一些时间将原有的 chunk 拆分到新 chunk,并且此操作不可逆。
- 如果新增 chunk size,已存在的 chunk 只会等到新的插入或更新操作将其扩充至新的大小。
- chunk size 的可调整范围为 1-1024MB。
Balancer 均衡器
MongoDB 的 balancer(均衡器)是监视每个分片的 chunk 数的一个后台进程。当分片上的 chunk 数达到特定迁移阈值时,均衡器会尝试在分片之间自动迁移块,使得每个分片的块的数量达到平衡。分片群集的平衡过程对用户和应用程序层完全透明,但在执行过程时可能会对性能产生一些影响。
从 MongoDB 3.4 开始,balancer 在配置服务器副本集(CSRS)的主服务器上运行,在 3.4 版本中,当平衡器进程处于活动状态时,主配置服务器的的 locks 集合通过修改 _id: "balancer"
文档会获取一个 balancer lock,该 balancer lock 不会被释放,是为了保证只有一个 mongos 实例能够在分片集群中执行管理任务。从 3.6 版本开始,均衡器不再需要 balancer lock。
均衡器可以动态的开启和关闭,也可以针对指定的集合开启和关闭,还可以手动控制均衡器迁移 chunk 的时间,避免在业务高峰期的时候迁移 chunk 从而影响集群性能。以下命令将均衡器的迁移 chunk 时间控制在凌晨 02 点至凌晨 06 点:
1 | use config |
环境准备
系统系统:CentOS7 64位
三台服务器:192.168.0.11/12/13
安装包:mongodb-linux-x86_64-4.2.5.tgz
服务器规划
服务器11 服务器12 服务器13 mongos mongos mongos config server config server config server shard server1 主节点 shard server1 副节点 shard server1 仲裁 shard server2 仲裁 shard server2 主节点 shard server2 副节点 shard server3 副节点 shard server3 仲裁 shard server3 主节点 端口分配
- mongos:20000
- config:21000
- shard1:27001
- shard2:27002
- shard3:27003
分片集群搭建
安装 mongodb
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat-tarball/
1 | tar -xzvf mongodb-linux-x86_64-4.2.5.tgz -C /usr/local/ |
创建数据和日志目录
分别在每台机器建立 conf、mongos、config、shard1、shard2、shard3 六个目录,因为 mongos 不存储数据,只需要建立日志文件目录即可。
1 | mkdir -p /usr/local/mongodb/conf /usr/local/mongodb/mongos/log /usr/local/mongodb/config/data /usr/local/mongodb/config/log /usr/local/mongodb/shard1/data /usr/local/mongodb/shard1/log /usr/local/mongodb/shard2/data /usr/local/mongodb/shard2/log /usr/local/mongodb/shard3/data /usr/local/mongodb/shard3/log |
配置环境变量
1 | vim /etc/profile |
添加如下内容:
1 | export MONGODB_HOME=/usr/local/mongodb |
使修改立即生效:
1 | source /etc/profile |
config server 配置服务器
mongodb3.4 以后要求配置服务器也创建副本集,不然集群搭建不成功。
添加配置文件
1 | vim /usr/local/mongodb/conf/config.conf |
配置文件内容:
1 | pidfilepath=/usr/local/mongodb/config/log/configsrv.pid |
启动三台服务器的 config server:
1 | mongod -f /usr/local/mongodb/conf/config.conf |
登录任意一台配置服务器:
1 | mongo --port 21000 |
定义 config server 副本集配置:
1 | config = { |
初始化 config server 副本集:
1 | rs.initiate(config) |
其中,_id : "configs"
应与配置文件中配置的 replicaction.replSetName
一致,"members" 中的 "host" 为三个节点的 ip 和 port
配置分片副本集(三台机器)
设置第一个分片副本集
添加配置文件:
1 | vim /usr/local/mongodb/conf/shard1.conf |
配置文件内容:
1 | pidfilepath=/usr/local/mongodb/shard1/log/shard1.pid |
启动三台服务器的 shard1 server:
1 | mongod -f /usr/local/mongodb/conf/shard1.conf |
登录任意一台非 arbiter 的 shard1 server:
1 | mongo --port 27001 |
使用 admin 数据库,定义 shard1 server 副本集配置:
第三个节点的 arbiterOnly: true
代表其为仲裁节点。
1 | use admin |
初始化 shard1 server 副本集:
1 | rs.initiate(config) |
设置第二个分片副本集
添加配置文件:
1 | vim /usr/local/mongodb/conf/shard2.conf |
配置文件内容:
1 | pidfilepath=/usr/local/mongodb/shard2/log/shard2.pid |
启动三台服务器的 shard2 server:
1 | mongod -f /usr/local/mongodb/conf/shard2.conf |
登录任意一台非 arbiter 的 shard2 server:
1 | mongo --port 27002 |
使用 admin 数据库,定义 shard2 server 副本集配置:
1 | use admin |
初始化 shard2 server 副本集:
1 | rs.initiate(config) |
设置第三个分片副本集
添加配置文件:
1 | vim /usr/local/mongodb/conf/shard3.conf |
配置文件内容:
1 | pidfilepath=/usr/local/mongodb/shard3/log/shard3.pid |
启动三台服务器的 shard3 server:
1 | mongod -f /usr/local/mongodb/conf/shard3.conf |
登录任意一台非 arbiter 的 shard3 server:
1 | mongo --port 27003 |
使用 admin 数据库,定义 shard3 server 副本集配置:
1 | use admin |
初始化 shard3 server 副本集:
1 | rs.initiate(config) |
配置路由服务器 mongos
先启动配置服务器和分片服务器,后启动路由实例:(三台机器)
添加配置文件:
1 | vim /usr/local/mongodb/conf/mongos.conf |
配置文件内容:
1 | pidfilepath=/usr/local/mongodb/mongos/log/mongos.pid |
启动三台服务器的 mongos server:
1 | mongos -f /usr/local/mongodb/conf/mongos.conf |
启用分片
目前搭建了 mongodb 配置服务器、路由服务器,各个分片服务器,不过应用程序连接到 mongos 路由服务器并不能使用分片机制,还需要在程序里设置分片配置,让分片生效。
登陆任意一台 mongos:
1 | mongo --port 20000 |
使用 admin 数据库,串联路由服务器与分片副本集:
1 | use admin |
查看集群状态:
1 | sh.status() |
测试分片
基于 Hashed 的分片
目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,现在我们希望插入数据时,数据能够自动分片。
首先连接到 mongos:
1 | mongo --port 20000 |
切换到 admin 数据库,让指定的数据库、指定的集合分片生效:
1 | use admin |
这里设置 test 数据库的 hashed
表需要分片,根据 _id
自动分片到 shard1,shard2,shard3 上面去,之所以这样设置是因为不是所有 mongodb 的数据库和表都需要分片!
查看此时的分片的状态:
1 | sh.status() |
切换到 test 数据库,测试分片配置结果:
1 | use test |
查看分片情况,省掉了部分无关信息:
1 | use test |
可以看到数据分到3个分片,各自分片数量为:
- shard1
"count": 33085
- shard2
"count": 33476
- shard3
"count": 33439
33085 + 33476 + 33085 = 100000
基于 Ranged 的分片
基于范围分片特别适合范围查找,因为可以直接定位到分片,所以效率很高。
为了方便看到基于 Ranged 分片的效果,我们先修改分片集群的 Chunk Size。
分片群集的默认块大小为 64 MB。 此默认块大小适用于大多数部署。但是,如果您发现自动迁移的 I/O超出了硬件的处理能力,则可能需要减小块大小。对于自动拆分和迁移,小的块大小会导致更快和更频繁的迁移。块大小的允许范围在 1 到 1024 MB(含)之间。
连接到 mongos:
1 | mongo --port 20000 |
切换到 config 数据库,使用 save() 来设置全局块大小的配置值:
1 | use config |
切换到 admin 数据库,让指定的数据库、指定的集合分片生效:
1 | use admin |
这里设置 test 数据库的 ranged
表需要分片,根据 age
自动分片到 shard1,shard2,shard3,查看此时的分片的状态:
1 | sh.status() |
切换到 test 数据库,测试分片配置结果:
1 | use test |
后期运维
启动关闭
mongodb 的启动顺序是,先启动配置服务器,再启动分片,最后启动 mongos
1 | mongod -f /usr/local/mongodb/conf/config.conf |
关闭时,直接 killall 杀掉所有进程
1 | killall mongod |
注意事项
- Mongodb 分片需要比较大的内存,搭建时分配比较大的内存空间,线上环境是 48G
- 每个 mongodb 实例,需要限制 wiredTigerCacheSizeGB 大小,即在每个配置文件中增加这个配置。根据经验,如果每台机器上的实例个数 n,
n * wiredTigerCacheSizeGB < 2/3
总内存 - Mongodb 分片后,每次 update 数据时,必须指定片键,没有片键无法 update
附录
清除数据和日志目录
1 | rm -rf config/data/* |
References
https://docs.mongodb.com/manual/sharding/
https://www.ibm.com/developerworks/cn/opensource/os-mongodb-sharded-cluster/index.html
http://www.ityouknow.com/mongodb/2017/08/05/mongodb-cluster-setup.html