MongoDB——集群搭建

Mongodb的三种集群方式的搭建:Replica Set / Sharding / Master-Slaver
官方已经不推荐Master-Slaver集群方式

一、Mongodb的副本集(Replica Set)集群节点角色介绍及选举过程

一个副本集ReplicaSet一般由一组mongod实例组成,这组mongod实例协调配合工作,共同向外提供高可用的数据库访问服务。副本集中的不同节点虽然都是mongod实例,但是角色上却有不同,一般分为三种:主节点、副本节点和仲裁者节点。

  • 主节点:负责所有的数据库写操作,默认情况下,主节点也负责处理所有的数据库读操作;

  • 副本节点:负责同步主节点的数据操作日志更新本地数据库,从而保证副本节点的数据和主节点上的数据的一致性;副本节点的从某种意义上来讲有点像赛跑,永远在追赶主节点的数据操作;

  • 仲裁节点:不负责保存具体的数据,只是在副本集进行主节点选举时提供自己的一个选票而已。

除了上面三个角色的节点外,还有几种其他节点,下面简单描述一下:

  • Secondary-Only:和副本节点一样保存了主节点的数据副本,但是在任何情况下都成为不了Primary主节点;

  • Hidden:这种类型的节点对于客户端程序是不可见的,同样不能成为Primary主节点,但是可以参与投票;

  • Delayed:这种类型的节点可以通过人为的设置,可以指定一个时间来延迟从主节点同步数据,Delayed成员主要用于从一些误操作中恢复旧数据,并且肯定不能成为主节点而且是Hidden的;

一般情况下,一个最小化的副本集由一个主节点、一个副本节点和一个仲裁节点组成,但是在实际使用过程中,大多数采用的是由一个主节点和两个副本节点组成的。

1. 一个最小化副本集的结构图如下所示:
MongoDB——集群搭建_第1张图片
在上图中,副本集中的主节点负责处理来自客户端的所有读写操作,并将所有的修改数据库的操作以日志Oplog的方式记录在本地;副本节点按照一定的频率主动异步的从主节点同步操作日志并作用于自己本机的数据库上,从而保证副本节点和主节点的数据一致性。副本集中的各个节点之间每2秒进行一个心跳请求,查询副本集中的其他节点的状态。
MongoDB——集群搭建_第2张图片
2. 如何操作读写分离

正常情况下,读写操作是操作在主节点上,写操作我们是无法进行控制的,只能作用在主节点上,但是对于读操作我们确实可以控制的(从副本节点上读取数据,从而达到读写分离),这时候我们就需要了解ReadPreference了,下面简单列下都有哪些模式可以供我们选择:

read preference是replica set中才会涉及到的概念,用于指定怎么读取数据。官方对read preference的解释是:
Read preference describes how MongoDB clients route read operations to the members of a replica set

1> primary:默认方式,所有的读操作都从主节点获取;

2> primaryPerferred:在大多数场景下直接从主节点进行读操作,如果主节点读操作不可用时,则从副本节点进行读取;

3> secondary:所有的读操作都只从副本节点进行读取;

4> secondaryPreferred:在大多数场景下,读操作都从副本节点进行读操作,如果副本节点不可用,那么考虑从主节点进行读取;

5> nearest:所有的读操作从网络延迟最小的节点上进行读操作;

3. 配置ReadPreference的方式

第一种:uri中配置
可以在uri中拼接readPreference=primaryPreferred
代码示例如下:

@Bean
    public MongoClient mongoClient() {
        String uriString = "mongodb://username:[email protected]:27017,"
                + "cluster0-shard-00-01-75shm.gcp.mongodb.net:27017,"
                + "cluster0-shard-00-02-75shm.gcp.mongodb.net:27017/?"
                + "ssl=true&replicaSet=Cluster0-shard-0&authSource=admin"
                + "&retryWrites=true&readPreference=primaryPreferred";
        MongoClientURI uri = new MongoClientURI(uriString);
        MongoClient mongoClient = new MongoClient(uri);
        return mongoClient;
    }

实际上,通过这种方式不止可以配置readPreference,还可以配置很多其它选项。

第二种:MongoClientOptions中配置
代码示例如下:

    @Bean
    public MongoClient mongoClient() {
        List<ServerAddress> saList = new ArrayList<>();
        saList.add(new ServerAddress("cluster0-shard-00-00-75shm.gcp.mongodb.net", 27017));
        saList.add(new ServerAddress("cluster0-shard-00-01-75shm.gcp.mongodb.net", 27017));
        saList.add(new ServerAddress("cluster0-shard-00-02-75shm.gcp.mongodb.net", 27017));
        
        char[] pwd =  "password".toCharArray();
                //第二个参数“admin”对应authSource,也就是authentication database.
        MongoCredential credential = MongoCredential.createCredential("username", "admin", pwd);
    
        //必须设置sslEnabled为true,否则会报MongoSocketReadException: Prematurely reached end of stream错误
        MongoClientOptions options = MongoClientOptions.builder()
                .readPreference(ReadPreference.primaryPreferred())
                .retryWrites(true)
                .requiredReplicaSetName("Cluster0-shard-0")
                .maxConnectionIdleTime(6000)
                .sslEnabled(true)
                .build();
        
        MongoClient mongoClient = new MongoClient(saList, credential, options);     
        return mongoClient;
    }

这里必须提醒的是必须设置sslEnabled为true,否则会报MongoSocketReadException。

4. 节点异常时处理解析

上面的结构是一个最小化的副本集结构,可以对外提供高可用的数据库访问能力,但是当主节点崩溃后或者暂时不可用时,整个副本集会发生什么呢?

1> 如果主节点发现自己和副本集中大多数节点无法连接时,为了保证数据一致性,会将自己降级为副本节点,从而阻止有操作对数据库进行修改;

2> 如果主节点崩溃了不可用了,那么副本集会内会进行一次选举操作,重新选举产生一个主节点;

5. 关于选举的过程
如下所示:
MongoDB——集群搭建_第3张图片
上图中,主节点Primary假设不可用了,那么另外两个副本节点在发现与主节点无法进行心跳时(正常情况下,每个节点每2秒跟其他节点进行一次心跳测试,如果在10s内未收到对方节点的回复,那么认为对方节点不可用),有资格成为主节点的副本节点就会向其他节点发起一个选举提议,基本的意思就是“我觉得我能成为主节点,你觉得呢?”,而其他节点在收到选举提议后会判断下面几个条件:

1> 副本集中是否有其他节点已经是primary了?

2> 自己的数据是否比请求成为主节点的副本节点上的数据更新?

3> 副本集中其他节点的数据是否比请求成为主节点的副本节点的数据更新?

  • 如果上面三个条件中只要有一个条件满足,那么都会认为对方的提议不可行,于是返回一个返回包给请求节点说“我觉得你成为primary不合适,停止选举吧!”,请求节点只要收到其他任何一个节点返回不合适,都会立刻停止选举,并将自己保持在secondary角色;
  • 但是如果上面三个条件都不满足,那么就会在返回包中回复说“我觉得你可以_”,那么此时请求包就会进入选举的第二阶段。
  • 请求节点会向其他节点发送一个确认的请求包,基本意思就是“我宣布自己是primary了,有人反对没?”,如果在这次确认过程中其他节点都没人反对,那么请求节点就将自己升级为primary节点,所有节点在30秒内不再进行其他选举投票决定;
  • 如果有节点此时认为请求节点不适合做primary,那么请求节点在收到反对回复后会保持自己的节点角色依然是secondary。

获得『大多数』成员投票支持的节点,会成为Primary,其余节点成为Secondary。『大多数』的定义
假设复制集内投票成员(后续介绍)数量为N,则大多数为 N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。
通常建议将复制集成员数量设置为奇数,从上表可以看出3个节点和4个节点的复制集都只能容忍1个节点失效,从『服务可用性』的角度看,其效果是一样的。(但无疑4个节点能提供更可靠的数据存储)

二、MongoDB 分片(Sharding)集群

副本集有个很大的缺陷,就是只能在主节点写操作,但是当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量,此时就可以通过在多台机器上分割数据,使得数据库系统能存储和处理更多的数据,就是分片技术Sharding,可以满足MongoDB数据量大量增长的需求。

1. 分片的目的

高数据量和吞吐量的数据库应用会对单机的性能造成较大压力,大的查询量会将单机的CPU耗尽,大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到磁盘IO上。为了解决这些问题,有两个基本的方法: 垂直扩展和水平扩展。

  • 垂直扩展:增加更多的CPU和存储资源来扩展容量。

  • 水平扩展:将数据集分布在多个服务器上。水平扩展即分片。

2. 分片设计思想

分片为应对高吞吐量与大数据量提供了方法。使用分片减少了每个分片需要处理的请求数,因此,通过水平扩展,集群可以提高自己的存储容量和吞吐量。举例来说,当插入一条数据时,应用只需要访问存储这条数据的分片,使用分片减少了每个分片存储的数据。

例如,如果数据库1tb的数据集,并有4个分片,然后每个分片可能仅持有256 GB的数据。如果有40个分片,那么每个切分可能只有25GB的数据。
MongoDB——集群搭建_第4张图片
3. 分片机制提供了如下三种优势

1> 对集群进行抽象,让集群“不可见”
MongoDB自带了一个叫做mongos的专有路由进程。mongos就是掌握统一路口的路由器,其会将客户端发来的请求准确无误的路由到集群中的一个或者一组服务器上,同时会把接收到的响应拼装起来发回到客户端。

2> 保证集群总是可读写
MongoDB通过多种途径来确保集群的可用性和可靠性。将MongoDB的分片和复制功能结合使用,在确保数据分片到多台服务器的同时,也确保了每分数据都有相应的备份,这样就可以确保有服务器换掉时,其他的从库可以立即接替坏掉的部分继续工作。

3>使集群易于扩展
当系统需要更多的空间和资源的时候,MongoDB使我们可以按需方便的扩充系统容量。

4. 分片集群架构

主要组件说明:

组件 说明
Config Server 存储集群所有节点、分片数据路由信息。默认需要配置3个Config Server节点。
Mongos 提供对外应用访问,所有操作均通过mongos执行。一般有多个mongos节点。数据迁移和数据自动平衡
Mongod 存储应用数据记录。一般有多个Mongod节点,达到数据分片目的。

MongoDB——集群搭建_第5张图片
上图是分片集群基本构造模式:

  • mongos :数据路由,和客户端打交道的模块。mongos本身没有任何数据,他也不知道该怎么处理这数据,去找config server,当数据写入时,MongoDB Cluster根据分片键设计写入数据。当外部语句发起数据查询时,MongoDB根据数据分布自动路由至指定节点返回数据。

  • config server:所有存、取数据的方式,所有shard节点的信息,分片功能的一些配置信息。可以理解为真实数据的元数据。

  • shard:真正的数据存储位置,以chunk为单位存数据。

总结:Mongos本身并不持久化数据,Sharded cluster所有的元数据都会存储到Config Server,而用户的数据会议分散存储到各个shard。Mongos启动后,会从配置服务器加载元数据,开始提供服务,将用户的请求正确路由到对应的碎片。

5. 集群中数据分布

1> Chunk是什么?

在一个shard server内部,MongoDB还是会把数据分为chunks,每个chunk代表这个shard server内部一部分数据。chunk的产生,会有以下两个用途:

  • Splitting:当一个chunk的大小超过配置中的chunk size时,MongoDB的后台进程会把这个chunk切分成更小的chunk,从而避免chunk过大的情况

  • Balancing:在MongoDB中,balancer是一个后台进程,负责chunk的迁移,从而均衡各个shard server的负载,系统初始1个chunk,chunk size默认值64M,生产库上选择适合业务的chunk size是最好的。MongoDB会自动拆分和迁移chunks。

分片集群的数据分布特点总结
1> 使用chunk来存储数据
2> 进群搭建完成之后,默认开启一个chunk,大小是64M,
3> 存储需求超过64M,chunk会进行分裂,如果单位时间存储需求很大,设置更大的chunk
4> chunk会被自动均衡迁移。

2> chunksize的选择

适合业务的chunksize是最好的。chunk的分裂和迁移非常消耗IO资源;chunk分裂的时机:在插入和更新时触发,读数据时不会分裂。

  • 小的chunksize:数据均衡是迁移速度快,数据分布更均匀。数据分裂频繁,路由节点消耗更多资源。
  • 大的chunksize:数据分裂少。数据块移动集中消耗IO资源。通常100-200M

3> chunk分裂及迁移

随着数据的增长,其中的数据大小超过了配置的chunk size,默认是64M,则这个chunk就会分裂成两个。数据的增长会让chunk分裂得越来越多。
MongoDB——集群搭建_第6张图片
这时候,各个shard 上的chunk数量就会不平衡。这时候,mongos中的balancer组件就会执行自动平衡。把chunk从chunk数量最多的shard节点移动到数量最少的节点。
MongoDB——集群搭建_第7张图片

chunkSize 对分裂及迁移的影响:

  • MongoDB 默认的 chunkSize 为64MB,如无特殊需求,建议保持默认值;chunkSize 会直接影响到 chunk 分裂、迁移的行为。
  • chunkSize 越小,chunk 分裂及迁移越多,数据分布越均衡;反之,chunkSize 越大,chunk 分裂及迁移会更少,但可能导致数据分布不均。
  • chunkSize 太小,容易出现 jumbo chunk(即shardKey 的某个取值出现频率很高,这些文档只能放到一个 chunk 里,无法再分裂)而无法迁移;chunkSize 越大,则可能出现 chunk 内文档数太多(chunk 内文档数不能超过 250000 )而无法迁移。
  • chunk 自动分裂只会在数据写入时触发,所以如果将 chunkSize 改小,系统需要一定的时间来将 chunk 分裂到指定的大小。
  • chunk 只会分裂,不会合并,所以即使将 chunkSize 改大,现有的 chunk 数量不会减少,但 chunk大小会随着写入不断增长,直到达到目标大小。

6. 数据区分

1> 分片键shard key

MongoDB中数据的分片是、以集合为基本单位的,集合中的数据通过片键(Shard key)被分成多部分。其实片键就是在集合中选一个键,用该键的值作为数据拆分的依据。

所以一个好的片键对分片至关重要。片键必须是一个索引,通过sh.shardCollection加会自动创建索引(前提是此集合不存在的情况下)。一个自增的片键对写入和数据均匀分布就不是很好,因为自增的片键总会在一个分片上写入,后续达到某个阀值可能会写到别的分片。但是按照片键查询会非常高效。

随机片键对数据的均匀分布效果很好。注意尽量避免在多个分片上进行查询。在所有分片上查询,mongos会对结果进行归并排序。

对集合进行分片时,你需要选择一个片键,片键是每条记录都必须包含的,且建立了索引的单个字段或复合字段,MongoDB按照片键将数据划分到不同的数据块中,并将数据块均衡地分布到所有分片中。

为了按照片键划分数据块,MongoDB使用基于范围的分片方式或者 基于哈希的分片方式。

总结:
分片键是不可变。
分片键必须有索引。
分片键大小限制512bytes。
分片键用于路由查询。
MongoDB不接受已进行collection级分片的collection上插入无分片
键的文档(也不支持空值插入)

2> 以范围为基础的分片Sharded Cluster

Sharded Cluster支持将单个集合的数据分散存储在多shard上,用户可以指定根据集合内文档的某个字段即shard key来进行范围分片(range sharding)。
MongoDB——集群搭建_第8张图片
对于基于范围的分片,MongoDB按照片键的范围把数据分成不同部分。

假设有一个数字的片键:想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点。MongoDB把这条直线划分为更短的不重叠的片段,并称之为数据块,每个数据块包含了片键在一定范围内的数据。在使用片键做范围划分的系统中,拥有”相近”片键的文档很可能存储在同一个数据块中,因此也会存储在同一个分片中。

3> 基于哈希的分片

分片过程中利用哈希索引作为分片的单个键,且哈希分片的片键只能使用一个字段,而基于哈希片键最大的好处就是保证数据在各个节点分布基本均匀。
MongoDB——集群搭建_第9张图片
对于基于哈希的分片,MongoDB计算一个字段的哈希值,并用这个哈希值来创建数据块。在使用基于哈希分片的系统中,拥有”相近”片键的文档很可能不会存储在同一个数据块中,因此数据的分离性更好一些。

Hash分片与范围分片互补,能将文档随机的分散到各个chunk,充分的扩展写能力,弥补了范围分片的不足,但不能高效的服务范围查询,所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。

4> 分片键选择建议

  • 递增的sharding key
    优点:数据文件挪动小。
    缺点:因为数据文件递增,所以会把insert的写IO永久放在最后一片上,造成最后一片的写热点。同时,随着最后一片的数据量增大,将不断的发生迁移至之前的片上。
  • 随机的sharding key
    优点:数据分布均匀,insert的写IO均匀分布在多个片上。
    缺点:大量的随机IO,磁盘不堪重荷。
  • 建议方法:混合型key
    大方向随机递增,小范围随机分布。

为了防止出现大量的chunk均衡迁移,可能造成的IO压力。我们需要设置合理分片使用策略(片键的选择、分片算法(range、hash))

分片注意:
分片键是不可变、分片键必须有索引、分片键大小限制512bytes、分片键用于路由查询。
MongoDB不接受已进行collection级分片的collection上插入无分片键的文档(也不支持空值插入)

https://www.cnblogs.com/clsn/p/8214345.html[参考文档]

你可能感兴趣的:(MongoDB,MongoDB)