分布式与微服务☞web组件☞MongoDB

500 阅读13分钟

基本介绍

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库(nosql)之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

  • 面向集合(collection)存储,易存储对象类型的数据。
  • 模式自由
  • 支持动态查询
  • 可通过网络访问
  • 支持查询
  • 支持复制和故障恢复
  • 支持完全索引,包含内部对象
  • 文件存储格式为BSON(一种JSON的扩展)
  • 自动处理碎片,以支持云计算层次的扩展性
  • 使用高效的二进制数据存储,包括大型对象(如视频等)

MongoDB数据库特点

  • 易扩展性 MongoDB使用分片技术对数据进行扩展,MongoDB能自动分片、自动转移分片里面的数据块,去掉了关系型数据库的关系型特性,数据之间没有关系。让每一个服务器里面存储的数据都是一样大小。这样就非常容易扩展。
  • 网站数据具有高性能 Mongo非常适合实时的插入,保留了关系型数据库即时查询的能力,并具备网站实时数据存储所需的复制及高度伸缩性。
  • 高伸缩性的场景 Mongo非常适合由数十或数百台服务器组成的数据库,Mongo的路线图中已经包含对MapReduce引擎的内置支持。
  • 存储动态性 相较于传统的数据库当要增加一个属性值的时候要对表大动,mongodb的面向文档的形式可以使其属性值轻意的增加和删除。而原来 的关系型数据库要实现这个需要有很多的属性表来支持。
  • 速度与持久性 MongoDB通过驱动调用写入时,可以立即得到返回得到成功的结果(即使是报错),这样让写入的速度更加快,当然会有一定的不安全性,完全依赖网络。

MongoDB架构

副本集模式 Replica Set

Replica Set 是一组 Mongodb 的副本集,就是一组mongod进程,这些进程维护同一个数据集合。副本集提供了数据冗余,提高了数据的可用性。在多台服务器保存数据可以避免因为一台服务器故障导致服务不可用或者数据丢失。

  • 数据多副本,在故障的时候,可以使用完的副本恢复服务。注意:这里是故障自动恢复
  • 读写分离,读的请求分流到副本上,减轻主(Primary)的读压力;
  • 节点直接互有心跳,可以感知集群的整体状态;

在副本集模式中,每个副本都是一个MongoDB实例,包含三类成员:

  • Primary主节点 只有 Primary 主节点是可读可写的,Primary 负责接收客户端所有的写请求,然后把操作记录到 oplog,再把数据同步到所有 Secondary 。一个 Replica Set 只有一个 Primary 节点,当 Primary 挂掉后,其他 Secondary 或者 Arbiter 节点会重新选举出来一个 Primary 节点,这样就又可以提供服务了。
    读请求默认是发到 Primary 节点处理,如果需要故意转发到 Secondary 需要客户端修改一下配置(注意:是客户端配置,决策权在客户端)。
    副本集模式与Master-Slave 模式的最大区别在于,Primary 角色是通过整个集群共同选举出来的,人人都可能成为 Primary ,人人最开始只是 Secondary ,而这个选举过程完全自动,不需要人为参与。
  • Secondary副本节点 副本节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证副本节点的数据与主节点一致。
    默认情况下,副本节点不支持外部读取,但可以设置。副本集的机制在于主节点出现故障的时候,余下的节点会选举出一个新的主节点,从而保证系统可以正常运行。(自动切换的,无需人工参与)
    Secondary 和 Master-Slave 模式的 Slave 角色的区别:最根本的一个不同在于:Secondary 相互有心跳,Secondary 可以作为数据源,Replica 可以是一种链式的复制模式。
  • Arbiter仲裁者 仲裁节点不复制数据,仅参与投票。由于它没有访问的压力,比较空闲,因此不容易出故障。
    由于副本集出现故障的时候,存活的节点必须大于副本集节点总数的一半,否则无法选举主节点,或者主节点会自动降级为从节点,整个副本集变为只读。
    因此,增加一个不容易出故障的仲裁节点,可以增加有效选票,降低整个副本集不可用的风险。仲裁节点可多于一个。也就是说只参与投票,不接收复制的数据,也不能成为活跃节点。

副本集模式的架构图如下:

注意:在副本集模式中,由于节点相互之间都有心跳,导致节点之间的心跳书以倍数增大,所以单个Replica Set集群的节点数不宜过大,否则会影响集群的整体性能。

  • Primary选举

复制集通过 replSetInitiate 命令(或mongo shell的rs.initiate())进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得『大多数』成员投票支持的节点,会成为Primary,其余节点成为Secondary。
因此,在副本集成员数上,一般设置为奇数最佳,否则可能会在投票时出现脑裂的问题。因为偶数个数的副本集,可能出现投票数相等的情况,这样无法选出 Primary节点。

MongoDB 分片集群 Sharding 模式

3.1 Sharding 模式简介

Replica Set 模式已经非常好的解决了MongoDB可用性的问题,但是为什么还会有分片集群呢?是因为数据量的问题。

一旦数据多,搞数据量和吞吐量的应用会对单机的性能造成较大的压力,大的查询量会影响到单机的CPU、内存等。这个时候 Replica Set 就不太管用了。为了解决这个问题,有两个基本的优化办法:

  • 纵向优化(垂直扩展):增加更多的CPU和存储资源来扩展容量。
  • 横向优化(水平扩展):将数据集分布在多个服务器上,水平扩展即分片,通俗来讲就是加节点。

纵向优化 通过不断提高机器的配置来解决问题,但始终追不上数据的增加。

横向优化 是业务上划分系统数据集,并在多台服务器上处理,做到容量和能力跟机器数量成正比。单台计算机的整体速度或容量可能不高,但是每台计算机只能处理全部工作量的一部分,因此与单台高速大容量服务器相比,可能提供更高的效率。缺点是软件的基础结构要支持,部署维护比较复杂。

在实际生产中,自然是使用横向优化会更好,如现在的分布式技术。MongoDB 的 Sharding 模式就是 MongoDB 横向扩容的一个架构实现。

Sharding cluster是一种可以水平扩展的模式,在数据量很大时特给力,实际大规模应用一般会采用这种架构去构建。sharding分片很好的解决了单台服务器磁盘空间、内存、cpu等硬件资源的限制问题,把数据水平拆分出去,降低单节点的访问压力。每个分片都是一个独立的数据库,所有的分片组合起来构成一个逻辑上的完整的数据库。因此,分片机制降低了每个分片的数据操作量及需要存储的数据量,达到多台服务器来应对不断增加的负载和数据的效果。

3.2 Sharding 集群架构

首先,Sharding 集群中以下三大模块:
Config Server:配置中心,存储集群所有节点、分片数据路由信息。默认需要配置3个Config Server节点。
Mongos路由:代理层,提供对外应用访问,所有操作均通过mongos执行。一般有多个mongos节点。数据迁移和数据自动平衡。
sharding分片:数据层, 存储应用数据记录。一般有多个Mongod节点,达到数据分片目的。

  • 代理层 代理层的组件也就是 mongos ,这是个无状态的组件,纯粹是路由功能。向上对接 Client ,收到 Client 写请求的时候,按照特定算法均衡散列到某一个 Shard 集群,然后数据就写到 Shard 集群了。收到读请求的时候,定位找到这个要读的对象在哪个 Shard 上,就把请求转发到这个 Shard 上,就能读到数据了。
  • 数据层 是存储数据的地方,每个shard(分片)包含被分片的数据集中的一个子集。每个分片可以被部署为 Replica Set 架构。
  • 配置中心 代理层是无状态的模块,数据层的每一个 Shard 是各自独立的,那么就需要有一个集群统配管理的地方,这个地方就是配置中心。
    配置中心记录的是集群所有节点、分片数据路由信息。这些信息也非常重要,不能单点存储,所以配置中心也是一个 Replica Set集群。

详细架构图:

  • Sharding 读写数据原理 单 Shard 集群是有限的,但 Shard 数量是无限的,Mongo 理论上能够提供近乎无限的空间,能够不断的横向扩容。那么现在唯一要解决的就是怎么去把用户数据存到这些 Shard 里?MongDB 是怎么做的?
    首先,要选一个字段(或者多个字段组合也可以)用来做 Key,这个 Key 可以是你任意指定的一个字段。我们现在就是要使用这个 Key 来,通过某种策略算出发往哪个 Shard 上。这个策略叫做:Sharding Strategy ,也就是分片策略。
    我们把 Sharding Key 作为输入,按照特点的分片策略计算出一个值,值的集合形成了一个值域,我们按照固定步长去切分这个值域,每一个片叫做 Chunk ,每个 Chunk 出生的时候就和某个 Shard 绑定起来,这个绑定关系存储在配置中心里。
    所以,我们看到 MongoDB 的用 Chunk 再做了一层抽象层,隔离了用户数据和 Shard 的位置,用户数据先按照分片策略算出落在哪个 Chunk 上,由于 Chunk 某一时刻只属于某一个 Shard,所以自然就知道用户数据存到哪个 Shard 了。
    mongos 作为路由模块其实就是寻路的组件,写的时候先算出用户 key 属于哪个 Chunk,然后找出这个 Chunk 属于哪个 Shard,最后把请求发给这个 Shard ,就能把数据写下去。读的时候也是类似,先算出用户 key 属于哪个 Chunk,然后找出这个 Chunk 属于哪个 Shard,最后把请求发给这个 Shard ,就能把数据读上来。
    实际情况下,mongos 不需要每次都和 Config Server 交互,大部分情况下只需要把 Chunk 的映射表 cache 一份在 mongos 的内存,就能减少一次网络交互,提高性能。
  • Sharding 的 Chunk 是什么 Chunk是指一个集合数据中的子集,可以简单理解为一个数据块,每个chunk都是基于片键(Shard Key)的范围取值,区间是左闭右开。chunk的产生,有以下用途:
  1. splitting:当一个chunk的大小超过了配置中的chunk size时,MongoDB的后台进程会把这个chunk切分成更小的chunk,从而避免chunk过大的情况;
  2. Balancing:在 MongoDB 中,balancer是一个后台进程,负责chunk的迁移,从而均衡各个shard server的负载,系统初始1个chunk,chunk size默认值是64M,生产库上选择适合业务的chunk size是最好的。MongoDB会自动拆分和迁移chunks。
  • 分片集群的数据分布:
  1. 使用chunk来存储数据
  2. 集群搭建完成之后,默认会开启一个chunk,大小是64M
  3. 当一个chunk的存储数据大小超过64M,chunk会进行分裂
  4. 当存储需求变大,集群上服务器的chunk数量变多,每个服务器的chunk数据严重失衡时,就会进行chunk的迁移 Chunk的大小
  5. chunk的大小需要结合业务来进行选择是最好的。
  6. chunk的大小不宜过小,如果chunk过小,好处是可以让数据更加均匀的分布,但是会导致chunk之间频繁的迁移,有一定的性能开销;如果chunk的大小过大,会导致数据分布不均匀。
  • 数据区分 MongoDB的分片是以集合为基本单位的,集合中的数据是通过片键被分成多个多个部分。这个片键其实就是在集合中选一个Key,用该Key的值作为数据拆分的依。上面提到的分片策略就是基于片键的。本质上 Sharding Strategy 是形成值域的策略而已,MongoDB 支持两种 Sharding Strategy:
  1. Hashed Sharding (哈希分片)
  2. Range Sharding (以范围为基础的分片) 哈希分片 对于基于哈希的分片,MongoDB计算一个Key的哈希值,并用这个哈希值来创建chunk。在使用基于哈希分片的系统中,拥有”相近”片键的文档很可能不会存储在同一个数据块中,因此数据的分离性更好一些。 基于范围的分片 基于范围的分片本质上是直接用 Key 本身来做值,形成的 Key Space 。

假设有一个数字的片键:想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点。MongoDB 把这条直线划分为更短的不重叠的片段,并称之为数据块,每个数据块包含了片键在一定范围内的数据。在使用片键做范围划分的系统中,拥有”相近”片键的文档很可能存储在同一个数据块中,因此也会存储在同一个分片中。 如上图这个例子,片键是name字段,对于片键的值“test_0","test_1","test_2",这样的key是紧挨着的,那么这些数据大概率就会被分到同一个chunk里面。 总结 哈希分片和范围分片都有各自的优缺点:

  • 哈希分片

    • 优点:计算速度快,均衡性好,纯随机
    • 缺点:排序列举性能差
  • 范围分片

    • 优点:排序列举性能高
    • 缺点:容易导致热点,如果片键的值都在一个范围内,那么大概率会被分配到同一个shard里,只盯着这一个shard读写,那么其他的shard就很空闲,浪费资源。 Hash分片与范围分片互补,能将文档随机的分散到各个chunk,充分的扩展写能力,弥补了范围分片的不足,但不能高效的服务范围查询,所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。