MongoDB全方位知识图谱
作者:sakychen,腾讯 CSIG 后台开发工程师
MongoDB 是一个强大的分布式存储引擎,天然支持高可用、分布式和灵活设计。MongoDB 的一个很重要的设计理念是:服务端只关注底层核心能力的输出,至于怎么用,就尽可能的将工作交个客户端去决策。这也就是 MongoDB 灵活性的保证,但是灵活性带来的代价就是使用成本的提升。与 MySql 相比,想要用好 MongoDB,减少在项目中出问题,用户需要掌握的东西更多。本文致力于全方位的介绍 MongoDB 的理论和应用知识,目标是让大家可以通过阅读这篇文章之后能够掌握 MongoDB 的常用知识,具备在实际项目中高效应用 MongoDB 的能力。
本文既有 MongoDB 基础知识也有相对深入的进阶知识,同时适用于对 MonogDB 感兴趣的初学者或者希望对 MongoDB 有更深入了解的业务开发者。
前言
以下是笔者在学习和使用 MongoDB 过程中总结的 MongoDB 知识图谱。本文将按照一下图谱中依次介绍 MongoDB 的一些核心内容。由于能力和篇幅有限,本文并不会对图谱中全部内容都做深入分析,后续将会针对特定条目做专门的分析。同时,如果图谱和内容中有错误或疏漏的地方,也请大家随意指正,笔者这边会积极修正和完善。
本文按照图谱从以下 3 个方面来介绍 MongoDB 相关知识: 基础知识 :主要介绍 MongoDB 的重要特性,No Schema、高可用、分布式扩展等特性,以及支撑这些特性的相关设计 应用接入: 主要介绍 MongoDB 的一些测试数据、接入方式、spring-data-mongo 应用以及使用 Mongo 的一些注意事项。 进阶知识: 主要介绍 MongoDB 的一些核心功能的设计实现,包括 WiredTiger 存储引擎介绍、Page/Chunk 等数据结构、一致性/高可用保证、索引等相关知识。
第一部分:基础知识
MongoDB 是基于文档的 NoSql 存储引擎。MongoDB 的数据库管理由数据库、Collection(集合,类似 MySql 的表)、Document(文档,类似 MySQL 的行)组成,每个 Document 都是一个类 JSON 结构 BSON 结构数据。
MongoDB 的核心特性是:No Schema、高可用、分布式(可平行扩展),另外 MongoDB 自带数据压缩功能,使得同样的数据存储所需的资源更少。本节将会依次介绍这些特性的基本知识,以及 MongoDB 是如何实现这些能力的。 1.1 No Schema
MongoDB 是文档型数据库,其文档组织结构是 BSON(Binary Serialized Document Format) 是类 JSON 的二进制存储格式,数据组织和访问方式完全和 JSON 一样。支持动态的添加字段、支持内嵌对象和数组对象,同时它也对 JSON 做了一些扩充,如支持 Date 和 BinData 数据类型。正是 BSON 这种字段灵活管理能力赋予了 Mongo 的 No Schema 或者 Schema Free 的特性。
No Schema 特性带来的好处包括: 强大的表现能力:对象嵌套和数组结构可以让数据库中的对象具备更高的表现能力,能够用更少的数据对象表现复杂的领域模型对象。 便于开发和快速迭代:灵活的字段管理,使得项目迭代新增字段非常容易 降低运维成本:数据对象结构变更不需要执行 DDL 语句,降低 Online 环境的数据库操作风险,特别是在海量数据分库分表场景。MongoDB 在提供 No Schema 特性基础上,提供了部分可选的 Schema 特性:Validation。其主要功能有包括: 规定某个 Document 对象必须包含某些字段 规定 Document 某个字段的数据类型 $(中 $开头的都是关键字) 规定 Document 某个字段的取值范围:可以是枚举 $,或者正则$regex上面的字段包含内嵌文档的,也就是说,你可以指定 Document 内任意一层 JSON 文件的字段属性。validator 的值有两种,一种是简单的 JSON Object,另一种是通过关键字 $jsonSchema 指定。以下是简单示例,想了解更多请参考官方文档:MongoDB JSON Schema 详解。
方式一: db.createCollection("saky_test_validation",{validator: { $and:[ {name:{$type: "string"}}, {status:{$in:["INIT","DEL"]}}] } })
方式二: db.createCollection("saky_test_validation", { validator: { $jsonSchema: { bsonType: "object", required: [ "name", "status", ], properties: { name: { bsonType: "string", description: "must be a string and is required" }, status: { enum: [ "INIT", "DEL"], description: "can only be one of the enum values and is required" } } }}) 1.2 MongoDB 的高可用
高可用是 MongoDB 最核心的功能之一,相信很多同学也是因为这一特性才想深入了解它的。那么本节就来说下 MongoDB 通过哪些方式来实现它的高可用,然后给予这些特性我们可以实现什么程度的高可用。
相信一旦提到高可用,浮现在大家脑海里会有如下几个问题: 是什么:MongoDB 高可用包括些什么功能?它能保证多大程度的高可用? 为什么:MongoDB 是怎样做到这些高可用的? 怎么用:我们需要做些怎样的配置或者使用才能享受到 MongoDB 的高可用特性?那么,带着这些问题,我们继续看下去,看完大家应该会对这些问题有所了解了。 1.2.1 MongDB 复制集群
MongoDB 高可用的基础是复制集群,复制集群本质来说就是一份数据存多份,保证一台机器挂掉了数据不会丢失。一个副本集至少有 3 个节点组成: 至少一个主节点(Primary): 负责整个集群的写操作入口,主节点挂掉之后会自动选出新的主节点。 一个或多个从节点(Secondary): 一般是 2 个或以上,从主节点同步数据,在主节点挂掉之后选举新节点。 零个或 1 个仲裁节点(Arbiter): 这个是为了节约资源或者多机房容灾用,只负责主节点选举时投票不存数据,保证能有节点获得多数赞成票。从上面的节点类型可以看出,一个三节点的复制集群可能是 PSS 或者 PSA 结构。PSA 结构优点是节约成本,但是缺点是 Primary 挂掉之后,一些依赖 majority(多数)特性的写功能出问题,因此一般不建议使用。复制集群确保数据一致性的核心设计是: Journal:Journal日志是 MongoDB 的预写日志 WAL,类似 MySQL 的 redo log,然后100ms一次将Journal 日子刷盘。Oplog :Oplog 是用来做主从复制的,类似 MySql 里的 binlog。MongoDB 的写操作都由 Primary 节点负责,Primary 节点会在写数据时会将操作记录在 Oplog 中,Secondary 节点通过拉取 oplog 信息,回放操作实现数据同步的。 Checkpoint :上面提到了 MongoDB 的写只写了内存和 Journal 日志 ,并没有做数据持久化,Checkpoint 就是将内存变更刷新到磁盘持久化的过程。MongoDB 会每60s一次将内存中的变更刷盘,并记录当前持久化点(checkpoint),以便数据库在重启后能快速恢复数据。 节点选举: MongoDB 的节点选举规则能够保证在Primary挂掉之后选取的新节点一定是集群中数据最全的一个,在3.3.1节点选举有说明具体实现。
从上面 4 点我们可以得出 MongoDB 高可用的如下结论: MongoDB 宕机重启之后可以通过 checkpoint 快速恢复上一个 60s 之前的数据。 MongoDB 最后一个 checkpoint 到宕机期间的数据可以通过 Journal日志回放恢复。 Journal日志 因为是 100ms 刷盘一次,因此至多会丢失 100ms 的数据(这个可以通过 WriteConcern 的参数控制不丢失,只是性能会受影响,适合可靠性要求非常严格的场景) 如果在写数据开启了多数写,那么就算 Primary 宕机了也是至多丢失 100ms 数据(可避免,同上) 1.2.2 读写策略
从上一小节发现,MongoDB 的高可用机制在不同的场景表现是不一样的。实际上,MongoDB 提供了一整套的机制让用户根据自己业务场景选择不同的策略。这里要说的就是 MongoDB 的读写策略,根据用户选取不同的读写策略,你会得到不同程度的数据可靠性和一致性保障。这些对业务开放者非常重要,因为你只有彻底掌握了这些知识,才能根据自己的业务场景选取合适的策略,同时兼顾读写性能和可靠性。
Write Concern —— 写策略
控制服务端一次写操作在什么情况下才返回客户端成功,由两个参数控制: w 参数:控制数据同步到多少个节点才算成功,取值范围 0 节点个数/majority。0 表示服务端收到请求就返回成功, major 表示同步到大多数(大于等于 N/2) 节点才返回成功。其它值表示具体的同步节点个数。 默认为 1,表示 Primary 写成功 就返回成功。 j 参数:控制单个节点是否完成 oplog 持久化到磁盘才返回成功,取值范围 true/false。默认 false,因此可能最多丢 100ms 数据。
Read Concern —— 读策略
控制客户端从什么节点读取数据,默认为 primary,具体参数及含义: primary:读主节点 primaryPreferred:优先读主节点,不存在时读从节点 secondary:读从节点 secondaryPreferred:优先读从节点,不存在时读主节点 nearest:就近读,不区分主节点还是从节点,只考虑节点延时。
更多信息可参考MongoDB 官方文档
Read Concern Level —— 读级别
这是一个非常有意思的参数,也是最不容易理解的异常参数。它主要控制的是读到的数据是不是最新的、是不是持久的,最新的和持久的是一对矛盾,最新的数据可能会被回滚,持久的数据可能不是最新的,这需要业务根据自己场景的容忍度做决策,前提是你的先知道有哪些,他们代表什么意义: local:直接从查询节点返回,不关心这些数据被同步到了多少个节点。存在被回滚的风险。 available:适用于分片集群,和 local 差不多,也存在被回滚的风险。 majority:返回被大多数节点确认过的数据,不会被回滚,前提是 WriteConcern=majority linearizable:适用于事务,读操作会等待在它开始前已经在执行的事务提交了才返回 snapshot:适用于事务,快照隔离,直接从快照去。
为了便于理解 local 和 majority,这里引用一下 MongoDB 官网上的一张 WriteConcern=majority 时写操作的过程图:
通过这张图可以看出,不同节点在不同阶段看待同一条数据满足的 level 是不同的:
1.3 MongoDB 的可扩展性 —— 分片集群
水平扩展是 MongoDB 的另一个核心特性,它是 MongoDB 支持海量数据存储的基础。MongoDB 天然的分布式特性使得它几乎可无限的横向扩展,你再也不用为 MySQL 分库分表的各种繁琐问题操碎心了。当然,我们这里不讨论 MongoDB 和其它存储引擎的对比,这个以后专门写下,这里只关注分片集群相关信息。 1.3.1 分片集群架构
MongoDB 的分片集群由如下三个部分组成:
Config :配置,本质上是一个 MongoDB 的副本集,负责存储集群的各种元数据和配置,如分片地址、chunks 等 Mongos :路由服务,不存具体数据,从 Config 获取集群配置讲请求转发到特定的分片,并且整合分片结果返回给客户端。 Mongod :一般将具体的单个分片叫 mongod,实质上每个分片都是一个单独的复制集群,具备负责集群的高可用特性。
其实分片集群的架构看起来和很多支持海量存储的设计很像,本质上都是将存储分片,然后在前面挂一个 proxy 做请求路由。但是, MongoDB 的分片集群有个非常重要的特性是其它数据库没有的,这个特性就是数据均衡 。数据分片一个绕不开的话题就是数据分布不均匀导致不同分片负载差异巨大,不能最大化利用集群资源。
MongoDB 的数据均衡的实现方式是: 分片集群上数据管理单元叫 chunk,一个 chunk 默认 64M,可选范围 1 1024M。 集群有多少个 chunk,每个 chunk 的范围,每个 chunk 是存在哪个分片上的,这些数据都是存储在 Config 的。 chunk 会在其内部包含的数据超过阈值时分裂成两个。 MongoDB 在运行时会自定检测不同分片上的 chunk 数,当发现最多和最少的差异超过阈值就会启动 chunk 迁移,使得每个分片上的 chunk 数差不多。 chunk 迁移过程叫 rebalance,会比较耗资源,因此一般要把它的执行时间设置到业务低峰期。
关于 chunk 更加深入的知识会在后面进阶知识里面讲解,这里就不展开了。 1.3.2 分片算法
MongoDB 支持两种分片算法来满足不同的查询需求: 区间分片: 可以按 shardkey 做区间查询的分片算法,直接按照 shardkey 的值来分片。 hash 分片:用的最多的分片算法,按 shardkey 的 hash 值来分片。hash 分片可以看作一种特殊的区间分片。
区间分片示例:
hash 分片示例:
从上面两张图可以看出: 分片的本质是 将 shardkey 按一定的函数变换 f(x) 之后的空间划分为一个个连续的段 ,每一段就是一个 chunk。 区间分片 f(x) = x;hash 分片 f(x) = hash(x) 每个 chunk 在空间中起始值是存在 Config 里面的。 当请求到 Mongos 的时候,根据 shardkey 的值算出 f(x) 的具体值为 f(shardkey),找到包含该值的 chunk,然后就能定位到数据的实际位置了。 1.4 数据压缩
MongoDB 的另外一个比较重要的特性是数据压缩,MongoDB 会自动把客户数据压缩之后再落盘,这样就可以节省存储空间。MongoDB 的数据压缩算法有多种: Snappy:默认的压缩算法,压缩比 3 5 倍 Zlib:高度压缩算法,压缩比 5 7 倍 前缀压缩:索引用的压缩算法,简单理解就是丢掉重复的前缀 zstd:MongoDB 4.2 之后新增的压缩算法,拥有更好的压缩率
现在推荐的 MongoDB 版本是 4.0,在这个版本下推荐使用 snappy 算法,虽然 zlib 有更高的压缩比,但是读写会有一定的性能波动,不适合核心业务,但是比较适合流水、日志等场景。
第二部分:应用接入
在掌握第一部分的基础上,基本上对 MongoDB 有一个比较直观的认识了,知道它是什么,有什么优势,适合什么场景。在此基础上,我们基本上已经可以判定 MongoDB 是否适合自己的业务了。如果适合,那么接下来就需要考虑怎么将其应用到业务中。在此之前,我们还得先对 MonoDB 的性能有个大致的了解,这样才能根据业务情况选取合适的配置。 2.1 基本性能测试
在使用 MongoDB 之前,需要对其功能和性能有一定的了解,才能判定是否符合自己的业务场景,以及需要注意些什么才能更好的使用。笔者这边对其做了一些测试,本测试是基于自己业务的一些数据特性,而且这边使用的是分片集群。因此有些测试项不同数据会有差异,如压缩比、读写性能具体值等。但是也有一些是共性的结论,如写性能随数据量递减并最终区域平稳。
压缩比
对比了同样数据在 Mongo 和 MySQL 下压缩比对比,可以看出 snapy 算法大概是 MySQL 的 3 倍,zlib 大概是 6 倍。
写性能
分片集群写性能在测试之后得到如下结论,这里分片是 4 核 8G 的配置: 写性能的瓶颈在单个分片上 当数据量小时是存内存读写,写性能很好,之后随着数量增加急剧下降,并最终趋于平稳,在 3000QPS。 少量简单的索引对写性能影响不大 分片集群批量写和逐条写性能无差异,而如果是复制集群批量写性能是逐条写性能的数倍。这点有点违背常识,具体原因这边还未找到。
读性能
分片集群的读分为三年种情况:按 shardkey 查询、按索引查询、其他查询。下面这些测试数据都是在单分片 2 亿以上的数据,这个时候 cache 已经不能完全换成业务数据了,如果数据量很小,数据全在 cache 这个性能应该会很好。 按 shardkey 查下,在 Mongos 处能算出具体的分片和 chunk,所以查询速度非常稳定,不会随着数据量变化。平均耗时 2ms 以内,4 核 8G 单分片 3 万 QPS。这种查询方式的瓶颈一般在 分片 Mongod 上,但也要注意 Mongos 配置不能太低。 按索引查询的时候,由于 Mongos 需要将数据全部转发到所有的分片,然后聚合全部结果返回客户端,因此性能瓶颈在 Mongos 上。测试 Mongos 8 核 16G + 10 分片情况下,单个 Mongos 的性能在 1400QPS,平均时延 10ms。业务场景索引是唯一的,因此如果索引数据不唯一,后端分片数更多,这个性能还会更低。 如果不按 shardkey 和索引查询因为涉及全表扫描,因此在数据量上千万之后基本不可用Mongos 有点特殊情况要注意的,就是客户端请求会到哪个 Mongos 是通过客户端 ip 的 hash 值决定的,因此同一个客户端所有请求一定会到同一个 Mongos,如果客户端过少的时候还会出现 Mongos 负载不均问题。 2.2 分片选择
在了解了 MongoDB 的基本性能数据之后,就可以根据自己的业务需求选取合适的配置了。如果是分片集群,其中最重要的就是分片选取,包括: 需要多少个 Mongos 需要分为多少个分片 分片键和分片算法用什么
关于前面两点,其实在知道各种性能参数之后就很简单了,前人已经总结出了相关的公式,我这里就简单把图再贴一下。
2.3 spring-data-mongo
MonogDB 官方提供了各种语言的 Client,这些 Client 是对 mongo 原始命令的封装。笔者这边是使用的 java,因此并未直接使用 MongoDB 官方的客户端,而是经过二次封装之后的 spring-data-mongo。好处是可以不用他关心底层的设计如连接管理、POJO 转换等。 2.3.1 接入步骤
spring-data-mongo 的使用方式非常简单。
第一步:引入 jar 包 org.springframework.boot spring-boot-starter-data-mongodb
第二步:ymal 配置 spring: data: mongodb: host: {{.MONGO_HOST}} port: {{.MONGO_PORT}} database: {{.MONGO_DB}} username: {{.MONGO_USER}} password: {{.MONGO_PASS}}
这里有个两个要注意: 权限,MongoDB 的权限是到数据级别的,所有配置的 username 必须有 database 那个库的权限,要不然会连不上。 这种方式配置没有指定读写 concern,如果需要在连接上指定的话,需要用 uri 的方式来配置,两种配置方式是不兼容的,或者自己初始化 MongoTemplate。
关于配置,跟多的可以在 IDEA 里面搜索 Mongo AutoConfiguration 查看源码,具体就是这个类:org.springframework.boot.autoconfigure.mongo.MongoProperties
关于自己初始化 MongoTemplate 的方式是: @Configuration public class MyMongoConfig { @Primary @Bean public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter){ MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory,mongoConverter); mongoTemplate.setWriteConcern(WriteConcern.MAJORITY); return mongoTemplate; } }
第三步:使用 MongoTemplate
在完成上面这些之后,就可以在代码里面注入 MongoTemplate,然后使用各种增删改查接口了。 2.3.2 批量操作注意事项
MongoDB Client 的批量操作有两种方式: 一条命令操作批量数据: insertAll,updateMany 等 批量提交一批命令: bulkOps,这种方式节省的就是客户端与服务端的交互次数bulkOps 的方式会比另外一种方式在性能上低一些。这两种方式到引擎层面具体执行时都是一条条语句单独执行,它们有一个很重要的参数: ordered ,这个参数的作用是控制批量操作在引擎内最终执行时是并行的还是穿行的。其默认值是 true。 true :批量命令窜行执行,遇到某个命令错误时就退出并报错,这个和事物不一样,它不会回滚已经执行成功的命令,如批量插入如果某条数据主键冲突了,那么它前面的数据都会插入成功,后面的会不执行。 false :批量命令并行执行,单个命令错误不影响其它,在执行结构里会返回错误的部分。还是以批量插入为例,这种模式下只会是主键冲突那条插入失败,其他都会成功。显然,false 模式下插入耗时会低一些,但是 MongoTemplate 的 insertAll 函数是在内部写死的 true。因此,如果想用 false 模式,需要自己继承 MongoTemplate 然后重写里面的 insertDocumentList 方法。 public class MyMongoTemplate extends MongoTemplate { @Override protected List
价格便宜不踩雷八款值得买的小米产品虽然双11已经过去了,但是近期很多数码产品的售价依然非常好,而且紧接着就要到双12了。最近笔者看到几款小米和米家的产品非常好,而且价格依然在市场中具有一定的优势。更重要的是,这几款
雷军太不讲武德,6000mAh大电池,899买到128G熟悉手机圈的朋友基本都知道,现在说到手机性价比,最让人敬佩的当属红米。在红米手机上,雷军投入了很大成本,本着薄利多销的原则,红米手机可以说是目前国产中利润最低的机型,正是因为如此,
市值一夜飙涨4600亿,苹果智能汽车要来了秘密开发7年后,苹果汽车终于要来了?11月18日,据彭博社消息,苹果已完成第一代汽车芯片的大部分工作,在全自动驾驶系统方面达到关键里程碑。同时,苹果公司内部目标可能是在4年内(20
错投雪梨押空李子柒这家公司砸进千万美元的苦,谁懂?本文来源时代财经作者幸雯雯不到半个月,税务部门又出手,严查追征网络主播逃脱的税款。这次追讨的是知名网红雪梨(本名朱宸慧)和林珊珊,二人被杭州市税务部门依法追缴税款加收滞纳金并处罚款
小米动力电池业务再下一城从整车制造电机及零部件到锂电池研发制造,覆盖整车生产多个关键环节,旨在复制其手机产业生态链的思路,打通电动汽车产业链各环节。11月18日,小米汽车科技有限公司成立,法定代表人雷军,
鸿蒙新机被炒到29999元!堪称年度理财产品,真有这么香吗?对于很多比较稀有的手机来说,往往被黄牛们盯上!比如,最近华为突然官宣的华为MateX2典藏版,可以说这是目前设计最完美综合实力最强的折叠屏旗舰,因为现货确实是太稀少了,所有这款旗舰
高通宣布骁龙品牌独立将改变产品命名体系11月23日高通官方宣布,旗下的骁龙未来将成为独立的产品品牌,并将采用简化一致的产品命名体系。其中骁龙移动平台的命名将会变为一位数字加上代际编号,这一命名规则将在新一代旗舰移动平台
五款热门平板电脑选购指南苹果华为,你选谁?可以像智能手机一样,通过触摸屏幕进行操作的平板电脑,它可以用于各种用途,例如观看视频玩游戏和创作绘图。还有一款高性能机型,可以进行剪辑视频,因此,越来越多的人将其用作笔记本电脑的替
新能源车风电需求如火如荼这一稀缺资源拍卖价水涨船高新能源车中该产品价值增长近15倍,未来该材料或将逐渐取代大多数硅基产品市场规模或暴涨30倍?燃料电池车前10月产销量已增长超40美国众议院通过1。75万亿美元刺激法案,首次针对储能
近一年收益100。74,398051太可了好的投资一定要顺应趋势,顺势而为!站在当下不论从政策倾向还是产业趋势看以新能源汽车,光伏等为代表的科技成长在未来几年将是高景气度的重要赛道之一,并且具有中长期投资机会。小海家有只基
HAKIIICE哈氪零度蓝牙耳机,冬天里的一把火HAKIIICE哈氪零度蓝牙耳机,冬天里的一把火蓝牙耳机自己用过很多,耳塞式,骨传导,半入耳等,有很多,用过的品牌也是数不胜数。当拿到HAKIIICE哈氪零度的时候,自己被震撼了。