[TOC]
综述
Mongodb一共有三种集群搭建的方式:
- Replica Set(副本集)
- Sharding(切片)
- Master-Slaver(主从,目前已不推荐使用了)
其中,Sharding集群也是三种集群中最复杂的。副本集比起主从可以实现故障转移, 经常使用。
MongoDB 目前已不推荐使用主从模式,取而代之的是副本集模式。副本集其实一种互为主从的关系,可理解为主主。副本集指将数据复制,多份保存,不同服务器保存同一份数据,在出现故障时自动切换。对应的是数据冗余、备份、镜像、读写分离、高可用性等关键词。
而分片则指为处理大量数据,将数据分开存储,不同服务器保存不同的数据,它们的数据总和即为整个数据集。追求的是高性能。在生产环境中,通常是这两种技术结合使用,分片+副本集。
主从集群
主从复制是MongoDB最常用的复制方式,也是一个简单的数据库同步备份的集群技术,这种方式很灵活.可用于备份,故障恢复,读扩展等. 最基本的设置方式就是建立一个主节点和一个或多个从节点,每个从节点要知道主节点的地址。采用双机备份后主节点挂掉了后从节点可以接替主机继续服务。所以这种模式比单节点的高可用性要好很多。
配置主从复制的注意点
- 在数据库集群中要明确的知道谁是主服务器,主服务器只有一台.
- 从服务器要知道自己的数据源也就是对应的主服务是谁.
- –master用来确定主服务器, –slave 和 –source 来控制从服务器
主从配置
1 | .............master-node节点配置............. |
如上配置后,在主节点写入的数据就会同步到从节点
主从复制原理
在主从结构中,主节点的操作记录成为oplog(operation log)。oplog存储在一个系统数据库local的集合oplog.$main 中,这个集合的每个文档都代表主节点上执行的一个操作。 从服务器会定期从主服务器中获取oplog记录,然后在本机上执行。对于存储oplog的集合,MongoDB采用的是固定集合,也就是说随着操作过多,新的操作会覆盖旧的操作。
在上面slave-node从节点的local数据库中,存在一个集合sources。这个集合就保存了这个服务器的主服务器是谁
1 | use local |
副本集集群(Replica Sets)
mongodb 不推荐主从复制,推荐建立副本集(Replica Set)来保证1个服务挂了,可以有其他服务顶上,程序正常运行,几个服务的数据都是一样的,后台自动同步。主从复制其实就是一个单副本的应用,没有很好的扩展性和容错性。然而副本集具有多个副本保证了容错性,就算一个副本挂掉了还有很多个副本存在,并且解决了”主节点挂掉后,整个集群内会自动切换”的问题。副本集比传统的Master-Slave主从复制有改进的地方就是它可以进行故障的自动转移,如果我们停掉复制集中的一个成员,那么剩余成员会再自动选举一个成员,作为主库。
在一些场景,可以使用副本集来扩展读性能,客户端有能力发送读写操作给不同的服务器。也可以在不同的数据中心获取不同的副本来扩展分布式应用的能力。mongodb副本集是一组拥有相同数据的mongodb实例,主mongodb接受所有的写操作,所有的其他实例可以接受主实例的操作以保持数据同步。主实例接受客户的写操作,副本集只能有一个主实例,因为为了维持数据一致性,只有一个实例可写,主实例的日志保存在oplog。
异步复制
副本节点同步主节点的操作是异步的,这会导致副本集无法返回最新的数据给客户端程序。其实这是典型CAP问题,一致性(Consistency),可用性(Availability),分区容忍性(Partition tolerance)三者只能取其二,mongoDB的主从复制模式,实际上是取了A和P而放弃了C,仅仅保证最终一致性。其实无论负载如何, 数据不一致的延迟的是一定存在的,不过是时间长短而已。
解決异步复制的一致性问题
mongoDB实际上有处理该问题的API,{w: “majority”},即写的时候阻塞到写到大多数结点写完才算完成。有了这点还是不够的,因为你要读的从结点并不能保证一定在“大多数”之内。为了保证读结点在“大多数”之内{readConcern: “majority”}——多数结点有的才算有。但是这样的话一个请求要压在大多数节点上,违背了读写分离,分散数据库压力的初衷,而且也将写操作趋近于同步,影响性能。 于是单独搞了一个可以保证数据一致性的connection,以便需要数据一致性的时候使用,而其他操作则不使用该操作。
副本集的结构及原理
MongoDB 的副本集不同于以往的主从模式。在集群Master故障的时候,副本集可以自动投票,选举出新的Master,并引导其余的Slave服务器连接新的Master,而这个过程对于应用是透明的。可以说MongoDB的副本集是自带故障转移功能的主从复制。一旦 Master 节点故障,则会在其余节点中选举出一个新的 Master 节点。 并引导剩余节点连接到新的 Master 节点。这个过程对于应用是透明的。
心跳检测
整个集群需要保持一定的通信才能知道哪些节点活着哪些节点挂掉。mongodb节点会向副本集中的其他节点每两秒就会发送一次pings包,如果其他节点在10秒钟之内没有返回就标示为不能访问。每个节点内部都会维护一个状态映射表,表明当前每个节点是什么角色、日志时间戳等关键信息。如果是主节点,除了维护映射表外还需要检查自己能否和集群中内大部分节点通讯,如果不能则把自己降级为secondary只读节点。
数据同步
副本集同步分为初始化同步和keep复制。初始化同步指全量从主节点同步数据,如果主节点数据量比较大同步时间会比较长。而keep复制指初始化同步过后,节点之间的实时同步,一般是增量同步。初始化同步不只是在第一次才会被处罚,有以下两种情况会触发:
1)secondary第一次加入,这个是肯定的。
2)secondary落后的数据量超过了oplog的大小,这样也会被全量复制。
数据同步-副本集数据过程
当Primary节点完成数据操作后,Secondary会做出一系列的动作保证数据的同步:
1)检查自己local库的oplog.rs集合找出最近的时间戳。
2)检查Primary节点local库oplog.rs集合,找出大于此时间戳的记录。
3)将找到的记录插入到自己的oplog.rs集合中,并执行这些操作。
MongoDB 同步延迟问题
什么是同步延迟?
1 | 什么是同步延迟? |
同步延迟带来的问题
1 | 同步延迟带来的问题 |
主节点故障
1 | 如果主从延迟过大,主节点上会有很多数据更改没有同步到从节点上。这时候如果主节点故障,就有两种情况: |
数据丢失
1 | 如果你只有一个从节点,当主从延迟过大时,由于主节点只保存最近的一部分 oplog,可能会导致从节点青黄不接,不得不进行 resync 操作,全量从主节点同步数据。 |
三种节点
副本集包括三种节点:主节点、从节点、仲裁节点。
1 | 1)主节点负责处理客户端请求,读、写数据, 记录在其上所有操作的 oplog; |
Mongodb副本集环境部署记录
1 | [root@master-node ~]# cat /usr/local/mongodb/mongodb.conf |
客户端连接副本集
要正确连接复制集,需要先了解下MongoDB的Connection String URI,所有官方的driver都支持以Connection String的方式来连接MongoDB。
- mongodb:// 前缀,代表这是一个Connection String
- username:password@ 如果启用了鉴权,需要指定用户密码
- hostX:portX 复制集成员的ip:port信息,多个成员以逗号分割
- /database 鉴权时,用户帐号所属的数据库
- ?options 指定额外的连接选项
通过正确的Connection String来连接MongoDB复制集时,客户端会自动检测复制集的主备关系,当主备关系发生变化时,自动将写切换到新的主上,(这里猜测是轮训去获取当前的主从信息),以保证服务的高可用。
如何实现读写分离?
在options里添加readPreference=secondaryPreferred
即可实现,读请求优先到Secondary节点,从而实现读写分离的功能,更多读选项
参考Read preferences
如何限制连接数?
在options里添加maxPoolSize=xx即可将客户端连接池限制在xx以内。
如何保证数据写入到大多数节点后才返回?
在options里添加w= majority
即可保证写请求成功写入大多数节点才向客户端确认,更多写选项
参考Write Concern
Mongodb分片集群(Sharding)
Sharding cluster是一种可以水平扩展的模式,在数据量很大时特给力,实际大规模应用一般会采用这种架构去构建。sharding分片很好的解决了单台服务器磁盘空间、内存、cpu等硬件资源的限制问题,把数据水平拆分出去,降低单节点的访问压力。每个分片都是一个独立的数据库,所有的分片组合起来构成一个逻辑上的完整的数据库。因此,分片机制降低了每个分片的数据操作量及需要存储的数据量,达到多台服务器来应对不断增加的负载和数据的效果。
分片的基本思想就是:
将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分。通过一个名为 mongos 的路由进程进行操作,mongos 知道数据和片的对应关系(通过配置服务器)。 大部分使用场景都是解决磁盘空间的问题,对于写入有可能会变差,查询则尽量避免跨分片查询。
分片是指将数据拆分,将其分散存在不同机器上的过程.有时也叫分区.将数据分散在不同的机器上MongoDB支持自动分片,可以摆脱手动分片的管理.集群自动切分数据,做负载均衡
三种角色
要构建一个MongoDB Sharding Cluster(分片集群),需要三种角色
分片服务器(Shard Server)
mongod 实例,用于存储实际的数据块,实际生产环境中一个 shard server 角色可由几台机器组个一个 relica set 承担,防止主机单点故障。
高可用性的分片架构还需要对于每一个分片构建 replica set 副本集保 证分片的可靠性。生产环境通常是 2 个副本 + 1 个仲裁。
配置服务器(Config Server)
这是一个独立的mongod进程,保存集群和分片的元数据,即各分片包含了哪些数据的信息。最先开始建立,启用日志功能。像启动普通的 mongod 一样启动, 并指定 configsvr 选项。
由于mongos 本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos 第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,这个可不能丢失!就算挂掉其中一台,只要还有存货,mongodb 集群就不会挂掉。
路由服务器(Route Server)
mongos 实例,前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用起到一个路由的功能,供程序连接。本身不保存数据,在启动时从配置服务器加载集群信息,开启 mongos 进程需要知道配置服务器的地址,指定configdb选项。
mongos 是数据库集群请求的入口,所有的请求都通过 mongos 进行协调,不需要在应用程序添加一个路由选择器,mongos 自己就是一个请求分发中心,它负责把对应的数据请求转发到对应的 shard 服务器上。在生产环境通常有多个 mongos 作为请求的入口,防止其中一个挂掉所有的 mongodb 请求都没有办法操作。
分片集群分析
golang 连接分片集群
- 用户访问 mongos 跟访问单个 mongod 类似
- 所有 mongos 是对等关系,用户访问分片集群可通过任意一个或多个mongos
- mongos 本身是无状态的,可任意扩展,集群的服务能力为『Shard服务能力之和』与『mongos服务能力之和』的最小值。
- 访问分片集群时,最好将应用负载均匀的分散到多个 mongos 上
代码:
如何选择shard key?
shard key 片键决定了集群中一个集合的 documents 在不同 shards 中的分布.片键字段必须被索引,且在集合中的每条记录都不能为空,可以是单个字段或复合字段.
MongoDB使用片键的范围把数据分布在分片中,每个范围,又称为数据块,定义了一个不重叠的片键范围,MongoDB把数据块与他们存储的文档分布到集群中的不同分片中.
shard key 在分片中的主要特点:
- 数据索引:作为 shard key 首先作用就是作为数据索引,因为建立 shard key 之前的必要条件就是必须是数据索引
- 不可更改:shard key 是固定的,一旦确定后(写入数据库),将不可进行更改
- 随机性:shard key 一定要具有一定的随机性。如果没有选择好 shard key,造成顺序性,则数据会落在某个特定的节点中,造成某节点数据过多,而其他节点却没有数据的情况。
一个好的 shard key 应该具备的特点:
key 分布足够离散 (sufficient cardinality)
足够分散才能带来性能上的增加
写请求均匀分布 (evenly distributed write)
数据应该均匀分布在所有的数据节点上
尽量避免 scatter-gather 查询 (targeted read)
避免大范围的扫描查询
现有 Shard Key 类型
- 范围分片:通常能很好的支持基于 shard key 的范围查询
- Hash 分片:通常能将写入均衡分布到各个 shard,不过对范围查询支持不好
案例分析
以公司 IOT 项目为例,在对采集数据进行 sharding 选择片键时,我们很容易想到:
以
用户编号
范围分片 为片键这个方案我在最开始的时候也是第一个想到的。但是经过思考后,否决掉了。该方案虽然可以解决在查询某个用户数据时不用扫描所有数据节点,但是用因为户数达不到极大的量级,所以这样会使采集的数据在落盘的时候集中在某一个节点的某个数据块中。
所以,该方案不合适。
以
用户编号
哈希分片 为片键这个方案虽然可以解决范围分片带来的集中问题,但是,仍然不能满足应用的需求,因为应用在使用过程中,需要查询某个用户某一个时间段范围内的所有采集到的数据,如果使用这种方案,那么进行数据查询的时候需要对所有节点进行查询扫描。
所以,该方案不合适。
以
created_at
创建时间 范围分片 为片键虽然该方案可以解决连续读取一个时间段内的数据问题,但是新的写入都是连续的时间戳,同样都会请求到同一个 shard,造成写分布不均。
所以,该方案不合适。
以
created_at
创建时间 哈希分片 为片键该方案的利弊刚好和上一个方案相反。
所以,该方案不合适。
以
用户编号
哈希分片 +created_at
范围分片 为组合片键这个方案其实不用分析就知道为什么不合适了,理由同第2个方案。因为当选择 用户编号哈希方案为首的组合片键时,用户编号哈希分片已经可以分片出数据了,MongoDB就不用再考虑后者了,所以造成的影响和第2个方案类似。
所以,该方案不合适。
以
用户编号
+created_at
范围分片 为组合片键同一个 用户编号 的数据能根据时间戳进一步分散到多个数据块中,同时根据 created_at 查询时间范围的数据,能直接利用(
用户编号
+created_at
)复合索引来完成。所以,该方案合适。
指令sh.shardCollection(your_db.your_collection, {"user_identify": 1, "created_at": 1})