分布式存储系统——《高性能分布式存储MongoDB》_mongodb监控视频的存储的流程(3)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : 可选,MongoDB 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,用来指定mongod对写操作的回执行为比如写的行为是否需要确认。
举例:
db.集合名.update({条件},{$set:{字段名:值}},{multi:true})

writeConcern 包括以下字段:
{ w: , j: , wtimeout: }

w:指定写操作传播到的成员数量
比如:
w=1(默认):则要求得到写操作已经传播到独立的Mongod实例或副本集的primary成员的确认
w=0:则不要求确认写操作,可能会返回socket exceptions和 networking errors
w=“majority”:要求得到写操作已经传播到大多数具有存储数据具有投票的(data-bearing voting)成员(也就是 members[n].votes 值大于0的成员)的确认

j:要求得到Mongodb的写操作已经写到硬盘日志的确认
比如:
j=true:要求得到Mongodb(w指定的实例个数)的写操作已经写到硬盘日志的确认。j=true本身并不保证因为副本集故障而不会回滚。

wtimeout:指定write concern的时间限制,只适用于w>1的情况
wtimeout在超过指定时间后写操作会返回error,即使写操作最后执行成功,当这些写操作返回时,
MongoDB不会撤消在wtimeout时间限制之前执行成功的数据修改。
如果未指定wtimeout选项且未指定write concern级别,则写入操作将无限期阻止。 指定wtimeout值为0等同于没有wtimeout选项。


#### 数据删除



db.collection.remove(
,
{
justOne: ,
writeConcern:
}
)
参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值
false,则删除所有匹配条件的文档。
writeConcern :(可选)用来指定mongod对写操作的回执行为。


### MongoDB聚合操作


#### 聚合操作简介


聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。一般都是将记录按条件分组之后进行一系列求最大值,最小值,平均值的简单操作,也可以对记录进行复杂数据统计,数据挖掘的操作。聚合操作的输入是集中的文档,输出可以是一个文档也可以是多个文档。


#### MongoDB 聚合操作分类


* 单目的聚合操作(Single Purpose Aggregation Operation)

db.lg_resume_preview.find({}).count()

* 聚合管道(Aggregation Pipeline)
* MapReduce 编程模型


#### 聚合管道


MongoDB中聚合(aggregate)主要用于`统计数据`(诸如统计平均值,求和等),并返回计算后的数据结果。表达式:处理输入文档并输出。表达式只能用于计算当前聚合管道的文档,不能处理其它的文档。



db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
// 如:
db.lg_resume_preview.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"city",city_count:{$sum:1}}}])




| 表达式 | 描述 |
| --- | --- |
| $sum | 计算总和 |
| $avg | 计算平均值 |
| $min | 获取集合中所有文档对应值得最小值 |
| $max | 获取集合中所有文档对应值得最大值 |
| $push | 在结果文档中插入值到一个数组中 |
| $addToSet | 在结果文档中插入值到一个数组中,但数据不重复 |
| $first | 根据资源文档的排序获取第一个文档数据 |
| $last | 根据资源文档的排序获取最后一个文档数据 |


MongoDB 中使用 `db.COLLECTION_NAME.aggregate([{},...])`方法来构建和使用聚合管道,每个文档通过一个由一个或者多个阶段(stage)组成的管道,经过一系列的处理,输出相应的结果。  
 MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。


这里我们介绍一下聚合框架中常用的几个操作:


* $group:将集合中的文档分组,可用于统计结果。
* $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
* $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
* $limit:用来限制MongoDB聚合管道返回的文档数。
* $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
* $sort:将输入文档排序后输出。
* $geoNear:输出接近某一地理位置的有序文档。


**举个栗子**



db.lg_resume_preview.aggregate(
[
{KaTeX parse error: Expected '}', got 'EOF' at end of input: group : {_id: "city", avgSal:{ a v g : " avg:" avg:"salary"}}},
{KaTeX parse error: Expected '}', got 'EOF' at end of input: …ject : {city: "city", salary : "KaTeX parse error: Expected 'EOF', got '}' at position 8: avgSal"}̲} ] ) db.lg_re…group:{_id: "KaTeX parse error: Expected '}', got 'EOF' at end of input: city",count:{sum : 1}}},
{KaTeX parse error: Expected '}', got 'EOF' at end of input: match:{count:{gt:1}}}
]
)


#### MapReduce编程模型


Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑。MongoDB不允许Pipeline的单个聚合操作占用过多的系统内存,如果一个聚合操作消耗20%以上的内存,那么MongoDB直接停止操作,并向客户端输出错误消息。


MapReduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。



db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: collection,
query: document,
sort: document,
limit: number,
finalize: ,
verbose:
}
)


使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历collection 中所有的记录, 将 key 与 value 传递给 Reduce 函数进行处理。  
 **参数说明**


* map:是JavaScript 函数,负责将每一个输入文档转换为零或多个文档,生成键值对序列,作为reduce 函数参数
* reduce:是JavaScript 函数,对map操作的输出做合并的化简的操作(将key-value变成keyvalues,也就是把values数组变成一个单一的值value)
* out:统计结果存放集合
* query: 一个筛选条件,只有满足条件的文档才会调用map函数。
* sort: 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
* limit: 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)
* finalize:可以对reduce输出结果再一次修改
* verbose:是否包括结果信息中的时间信息,默认为fasle



db.lg_resume_preview.mapReduce(
function() { emit(this.city,this.expectSalary); },
function(key, value) {return Array.avg(value)},
{
query:{expectSalary:{$gt: 15000}},
out:“cityAvgSal”
}
)


## MongoDB索引index


### 什么是索引


索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。索引目标是提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),数据量大时严重降低了查询效率。默认情况下Mongo在一个集合(collection)创建时,自动地对集合的\_id创建了唯一索引。


### 索引类型


#### 单键索引


MongoDB支持所有数据类型中的单个字段索引,并且可以在文档的任何字段上定义。对于单个字段索引,索引键的排序顺序无关紧要,因为MongoDB可以在任一方向读取索引。 单个例上创建索引:`db.集合名.createIndex({"字段名":排序方式})`  
 **特殊的单键索引 过期索引 TTL ( Time To Live)**  
 TTL索引是MongoDB中一种特殊的索引, 可以支持文档在一定时间之后自动过期删除,目前TTL索引只能在单字段上建立,并且字段类型必须是日期类型。



db.集合名.createIndex({“日期字段”:排序方式},{expireAfterSeconds:秒数})


#### 复合索引


通常我们需要在多个字段的基础上搜索表/集合,这是非常频繁的。 如果是这种情况,我们可能会考虑在MongoDB中制作复合索引。 复合索引支持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更大域。  
 制作复合索引时要注意的重要事项包括:字段顺序与索引方向。



db.集合名.createIndex({“字段名1”:“排序方式1”,“字段名2”:“排序方式2”})


#### 多建索引


针对属性包含数组数据的情况,MongoDB支持针对数组中每一个element创建索引,Multikeyindexes支持strings,numbers和nested documents


#### 地理空间索引


针对地理空间坐标数据创建索引。2dsphere索引,用于存储和查找球面上的点;2d索引,用于存储和查找平面上的点



db.company.insert(
{
loc : { type: “Point”, coordinates: [ 116.482451, 39.914176 ] },
name: “大望路地铁”,
category : “Parks”
}
)
db.company.ensureIndex( { loc : “2dsphere” } )
参数不是1或-1,为2dsphere 或者 2d。还可以建立组合索引。
db.company.find({
“loc” : {
KaTeX parse error: Expected '}', got 'EOF' at end of input: …Within" : { "center”:[[116.482451,39.914176],0.05]
}
}


#### 全文索引


MongoDB提供了针对string内容的文本查询,Text Index支持任意属性值为string或string数组元素的索引查询。**注意:一个集合仅支持最多一个Text Index,中文分词不理想 推荐ES。**



db.集合.createIndex({“字段”:“text”})


#### 哈希索引


针对属性的哈希值进行索引查询,当要使用Hashed index时,MongoDB能够自动的计算hash值,无需程序计算hash值。注:hash index仅支持等于查询,不支持范围查询。



db.集合.createIndex({“字段”:“hashed”})


### 索引和Explain分析


#### 索引管理


创建索引并在后台运行



db.collection_name.createIndex({“字段”:排序方式},{“background”:true})


获取针对某个集合的索引



db.collection_name.getIndexes()


索引的大小



db.collection_name.totalIndexSize()


索引的重建



db.collection.reIndex()


索引的删除



db.collection_name.dropIndex(“index_name”)
db.collection_name.dropIndexes()


#### explain分析


使用js循环 插入100万条数据 不使用索引字段 查询查看执行计划 ,然后给某个字段建立索引,使用索引字段作为查询条件 再查看执行计划进行分析  
 explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。


* queryPlanner:queryPlanner是默认参数,具体执行计划信息参考下面的表格。
* executionStats:executionStats会返回执行计划的一些统计信息(有些版本中和allPlansExecution等同)。
* allPlansExecution:allPlansExecution用来获取所有执行计划,结果参数基本与上文相同


**1、queryPlanner 默认参数**  
 暂时略,找时间详细写完。


### 慢查询分析


**1.开启内置的查询分析器,记录读写操作效率**  
 db.setProfilingLevel(n,m),n的取值可选0,1,2


* 0表示不记录
* 1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值
* 2表示记录所有的读写操作


**2.查询监控结果**



db.system.profile.find().sort({millis:-1}).limit(3)


**3.分析慢速查询**  
 应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等  
 **4.解读explain结果 确定是否缺少索引**


### MongoDB索引底层实现原理分析


MongoDB 是文档型的数据库,它使用BSON 格式保存数据,比关系型数据库存储更方便。比如之前关系型数据库中处理用户、订单等数据要建立对应的表,还要建立它们之间的关联关系。但是BSON就不一样了,我们可以把一条数据和这条数据对应的数据都存入一个BSON对象中,这种形式更简单,通俗易懂。MySql是关系型数据库,数据的关联性是非常强的,区间访问是常见的一种情况,底层索引组织数据使用B+树,B+树由于数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易的进行区间遍历甚至全部遍历。MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,单次查询从结构上来看要快于MySql。


**B-树是一种自平衡的搜索树,形式很简单:**  
 B-树特点


* 多路 非二叉树
* 每个节点 既保存数据 又保存索引
* 搜索时 相当于二分查找  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210419083605230.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)


**B+树**


* 多路非二叉树
* 只有叶子节点保存数据
* 搜索时 相当于二分查找
* 增加了相邻节点的指针  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210419083749275.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)



> 
> 从上面我们可以看出最核心的区别主要有俩,一个是数据的保存位置,一个是相邻节点的指向。就是这俩造成了MongoDB和MySql的差别。  
>  (1)B+树相邻接点的指针可以大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和data 在一起 适合随机读写 ,而区间查找效率很差。  
>  (2)B+树更适合外部存储,也就是磁盘存储,使用B-结构的话,每次磁盘预读中的很多数据是用不上的数据。因此,它没能利用好磁盘预读的提供的数据。由于节点内无 data 域,每个节点能索引的范围更大更精确。  
>  (3)注意这个区别相当重要,是基于(1)(2)的,B-树每个节点即保存数据又保存索引 树的深度小,所以磁盘IO的次数很少,B+树只有叶子节点保存,较B树而言深度大磁盘IO多,但是区间访问比较好。
> 
> 
> 


## MongoDB应用实战


### MongoDB适用场景


● 网站数据:Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。  
 ● 缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载。  
 ● 大尺寸、低价值的数据:使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费,在此之前,很多时候程序员往往会选择传统的文件进行存储。  
 ● 高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置支持以及集群高可用的解决方案。  
 ● 用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。


### MongoDB行业具体应用场景


* 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新。
* 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
* 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
* 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
* 直播,使用 MongoDB 存储用户信息、礼物信息等。


### 如何抉择是否使用MongoDB




| 应用特征 | Yes / No |
| --- | --- |
| 应用不需要事务及复杂 join 支持 | 必须 Yes |
| 新应用,需求会变,数据模型无法确定,想快速迭代开发 | ? |
| 应用需要2000-3000以上的读写QPS(更高也可以) | ? |
| 应用需要TB甚至 PB 级别数据存储 | ? |
| 应用发展迅速,需要能快速水平扩展 | ? |
| 应用要求存储的数据不丢失 | ? |
| 应用需要99.999%高可用 | ? |
| 应用需要大量的地理位置查询、文本查询 | ? |


### Java访问MongoDB


**maven 依赖**



org.mongodb mongo-java-driver 3.10.1

**文档添加**



MongoClient mongoClient = new MongoClient(“192.168.211.133”, 37017);
MongoDatabase database = mongoClient.getDatabase(“lg_resume”);
MongoCollection collection = database.getCollection(“lg_resume_preview”);
Document document = Document.parse(“{name:‘lisi’,city:‘bj’,birth_day:new ISODate(‘2001-08-01’),expectSalary:18000}”);
collection.insertOne(document );
mongoClient.close();


**文档查询**



MongoClient mongoClient = new MongoClient(“192.168.211.133”, 37017);
MongoDatabase database = mongoClient.getDatabase(“lg_resume”);
MongoCollection collection = database.getCollection(“lg_resume_preview”);
Document sdoc=new Document();
//按expectSalary倒序
sdoc.append(“expectSalary”, -1);
FindIterable findIterable = collection.find().sort(sdoc);
for (Document document : findIterable) {
System.out.println(document);
}
mongoClient.close();


**文档查询过滤**



MongoClient mongoClient = new MongoClient(“192.168.211.133”, 37017);
MongoDatabase database = mongoClient.getDatabase(“lg_resume”);
MongoCollection collection = database.getCollection(“lg_resume_preview”);
Document sdoc=new Document();
//按expectSalary倒序
sdoc.append(“expectSalary”, -1);
FindIterable findIterable = collection.find(Filters.gt(“expectSalary”,21000)).sort(sdoc);
for (Document document : findIterable) {
System.out.println(document);
}
mongoClient.close();


### Spring访问MongoDB


**第1步:基于maven新建工程 导入依赖的包**



org.springframework.data spring-data-mongodb 2.0.9.RELEASE

**第2步:在配置文件中配置 MongoTemplate**



<?xml version="1.0" encoding="UTF-8"?>



<mongo:db-factory id=“mongoDbFactory” client-uri=“mongodb://192.168.211.133:37017/lg_resume”>
</mongo:db-factory>





<context:component-scan base-package=“com.lagou”></context:component-scan>


**第3步:DAO 实现类注入 MongoTemplate 完成增删改查**



@Autowired
protected MongoTemplate mongoTemplate;


**第4步: 从Spring容器中获取DAO对象 进行测试 (注意:要开启组件扫描)**


### SpringBoot访问MongoDB


#### MongoTemplate 的方式


**第1步:基于maven新建springboot工程**



org.springframework.boot spring-boot-starter-data-mongodb 2.2.2.RELEASE

**第2步: 配置文件application.properties**



spring.data.mongodb.host=192.168.211.133
spring.data.mongodb.port=37017
spring.data.mongodb.database=lg_resume


**第3步: DAO 实现类 注入 MongoTemplate 完成增删改查**



@Autowired
protected MongoTemplate mongoTemplate;


**第4步: 从Spring容器中获取DAO对象 进行测试**


#### MongoRepository 的方式


**第1步:基于maven新建springboot工程**



org.springframework.boot spring-boot-starter-data-mongodb 2.2.2.RELEASE

**第2步: 配置文件application.properties**



spring.data.mongodb.host=192.168.211.133
spring.data.mongodb.port=37017
spring.data.mongodb.database=lg_resume


**第3步:编写实体类 并在实体类上打@Document(“集合名”)**  
 **第4步:编写 Repository 接口 继承 MongoRepository**  
 方法具体参考:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation  
 如果内置方法不够用 就自己定义 如:定义find|read|get 等开头的方法进行查询  
 **第5步: 从Spring容器中获取Repository对象 进行测试**


## MongoDB架构


### MongoDB逻辑架构


MongoDB 与 MySQL 中的架构相差不多,底层都使用了可插拔的存储引擎以满足用户的不同需要。用户可以根据程序的数据特征选择不同的存储引擎,在最新版本的 MongoDB 中使用了 WiredTiger 作为默认的存储引擎,WiredTiger 提供了不同粒度的并发控制和压缩机制,能够为不同种类的应用提供了最好的性能和存储率。在存储引擎上层的就是 MongoDB 的数据模型和查询语言了,由于 MongoDB 对数据的存储与 RDBMS有较大的差异,所以它创建了一套不同的数据模型和查询语言。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021041909352545.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)


### MongoDB的数据库类型


#### 描述数据模型


* 内嵌  
 内嵌的方式指的是把相关联的数据保存在同一个文档结构之中。MongoDB的文档结构允许一个字段或者一个数组内的值作为一个嵌套的文档。
* 引用  
 引用方式通过存储数据引用信息来实现两个不同文档之间的关联,应用程序可以通过解析这些数据引用来访问相关数据。


#### 如何选择数据模型


**选择内嵌:**


1. 数据对象之间有包含关系 ,一般是数据对象之间有一对多或者一对一的关系 。
2. 需要经常一起读取的数据。
3. 有 map-reduce/aggregation 需求的数据放在一起,这些操作都只能操作单个 collection。


**选择引用:**


1. 当内嵌数据会导致很多数据的重复,并且读性能的优势又不足于覆盖数据重复的弊端 。
2. 需要表达比较复杂的多对多关系的时候 。
3. 大型层次结果数据集 嵌套不要太深。


### MongoDB存储引擎


#### 存储引擎描述


存储引擎是MongoDB的核心组件,负责管理数据如何存储在硬盘和内存上。MongoDB支持的存储引擎有MMAPv1 ,WiredTiger和InMemory。InMemory存储引擎用于将数据只存储在内存中,只将少量的元数据(meta-data)和诊断日志(Diagnostic)存储到硬盘文件中,由于不需要Disk的IO操作,就能获取所需的数据,InMemory存储引擎大幅度降低了数据查询的延迟(Latency)。从mongodb3.2开始默认的存储引擎是WiredTiger,3.2版本之前的默认存储引擎是MMAPv1,mongodb4.x版本不再支持MMAPv1存储引擎。



storage:
journal:
enabled: true
dbPath: /data/mongo/
##是否一个库一个文件夹
directoryPerDB: true
##数据引擎
engine: wiredTiger
##WT引擎配置
WiredTiger:
engineConfig:
##WT最大使用cache(根据服务器实际情况调节)
cacheSizeGB: 2
##是否将索引也按数据库名单独存储
directoryForIndexes: true
journalCompressor:none (默认snappy)
##表压缩配置
collectionConfig:
blockCompressor: zlib (默认snappy,还可选none、zlib)
##索引配置
indexConfig:
prefixCompression: true


#### WiredTiger存储引擎优势




| 比较角度 | WiredTiger | MMAPV1 |
| --- | --- | --- |
| 文档空间分配方式 | 使用b-tree存储 | 线性存储 需要padding |
| 并发级别 | 文档级别锁 | 表级锁 |
| 数据压缩 | snappy(默认) zlib | 无压缩 |
| 内存使用 | 指定内存大小 | 无 |
| cache使用 | 使用二级缓存 文件系统缓存 保存数据一致性 | 只有journal日志 |


#### WiredTiger引擎包含的文件和作用


* WiredTiger.basecfg: 存储基本配置信息,与 ConfigServer有关系
* WiredTiger.lock: 定义锁操作
* table\*.wt: 存储各张表的数据
* WiredTiger.wt: 存储table\* 的元数据
* WiredTiger.turtle: 存储WiredTiger.wt的元数据
* journal: 存储WAL(Write Ahead Log)


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210419094226692.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)


#### WiredTiger存储引擎实现原理


**写请求**  
 WiredTiger的写操作会默认写入 Cache ,并持久化到 WAL (Write Ahead Log),每60s或Log文件达到2G做一次 checkpoint (当然我们也可以通过在写入时传入 j: true 的参数强制 journal 文件的同步 ,writeConcern { w: , j: , wtimeout: }) 产生快照文件。WiredTiger初始化时,恢复至最新的快照状态,然后再根据WAL恢复数据,保证数据的完整性。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210419094338654.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)  
 Cache是基于BTree的,节点是一个page,root page是根节点,internal page是中间索引节点,leafpage真正存储数据,数据以page为单位读写。WiredTiger采用Copy on write的方式管理写操作(insert、update、delete),写操作会先缓存在cache里,持久化时,写操作不会在原来的leaf page上进行,而是写入新分配的page,每次checkpoint都会产生一个新的root page。


**checkpoint流程**



> 
> 1.对所有的table进行一次checkpoint,每个table的checkpoint的元数据更新至WiredTiger.wt  
>  2.对WiredTiger.wt进行checkpoint,将该table checkpoint的元数据更新至临时文件WiredTiger.turtle.set  
>  3.将WiredTiger.turtle.set重命名为WiredTiger.turtle。  
>  4.上述过程如果中间失败,WiredTiger在下次连接初始化时,首先将数据恢复至最新的快照状态,然后根据WAL恢复数据,以保证存储可靠性。
> 
> 
> 


**Journaling**  
 在数据库宕机时 , 为保证 MongoDB 中数据的持久性,MongoDB 使用了 Write Ahead Logging 向磁盘上的 journal 文件预先进行写入。除了 journal 日志,MongoDB 还使用检查点(checkpoint)来保证数据的一致性,当数据库发生宕机时,我们就需要 checkpoint 和 journal 文件协作完成数据的恢复工作。


1. 在数据文件中查找上一个检查点的标识符
2. 在 journal 文件中查找标识符对应的记录
3. 重做对应记录之后的全部操作


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210419094440484.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)


## MongoDB集群高可用


### MongoDB主从复制架构原理和缺陷


master-slave架构中master节点负责数据的读写,slave没有写入权限只负责读取数据  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210421002424127.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)



> 
> 在主从结构中,主节点的操作记录成为`oplog`(operation log)。oplog存储在系统数据库local的oplog.$main集合中,这个集合的每个文档都代表主节点上执行的一个操作。从服务器会定期从主服务器中获取oplog记录,然后在本机上执行!对于存储oplog的集合,MongoDB采用的是固定集合,也就是说随着操作过多,新的操作会覆盖旧的操作!
> 
> 
> 


主从结构没有自动故障转移功能,需要指定master和slave端,不推荐在生产中使用。



mongodb4.0后不再支持主从复制!

[main] Master/slave replication is no longer supported


### 复制集


#### 什么是复制集


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210421003018753.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)



> 
> 复制集是由一组拥有相同数据集的mongod实例做组成的集群。  
>  复制集是一个集群,它是2台及2台以上的服务器组成,以及复制集成员包括`Primary主节点`,`secondary从节点`和`投票节点`。  
>  复制集提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性,保证数据的安全性。
> 
> 
> 


#### 为什么要使用复制集


* 高可用  
 防止设备(服务器、网络)故障。  
 提供自动failover 功能。  
 技术来保证高可用
* 灾难恢复  
 当发生故障时,可以从其他节点恢复 用于备份。
* 功能隔离  
 我们可以在备节点上执行读操作,减少主节点的压力  
 比如:用于分析、报表,数据挖掘,系统任务等。


#### 复制集集群架构原理


一个复制集中`Primary节点`上能够完成`读写操作`,`Secondary节点`仅能用于`读操作`。Primary节点需要记录所有改变数据库状态的操作,这些记录保存在 `oplog`中,这个文件存储在`local 数据库`,各个Secondary节点通过此`oplog`来复制数据并应用于本地,保持本地的数据与主节点的一致。oplog 具有`幂等性`,即无论执行几次其结果一致,这个比 mysql 的二进制日志更好用。


**oplog的组成结构**



{
“ts” : Timestamp(1446011584, 2),
“h” : NumberLong(“1687359108795812092”),
“v” : 2,
“op” : “i”,
“ns” : “test.nosql”,
“o” : { “_id” : ObjectId(“563062c0b085733f34ab4129”), “name” : “mongodb”,
“score” : “10”}
}
ts:操作时间,当前timestamp + 计数器,计数器每秒都被重置
h:操作的全局唯一标识
v:oplog版本信息
op:操作类型
i:插入操作
u:更新操作
d:删除操作
c:执行命令(如createDatabase,dropDatabase)
n:空操作,特殊用途
ns:操作针对的集合
o:操作内容
o2:更新查询条件,仅update操作包含该字段


复制集数据同步分为`初始化同步`和`keep复制同步`。初始化同步指全量从主节点同步数据,如果Primary节点数据量比较大同步时间会比较长。而keep复制指初始化同步过后,节点之间的实时同步一般是增量同步。初始化同步有以下两种情况会触发:


* Secondary第一次加入。
* Secondary落后的数据量超过了oplog的大小,这样也会被全量复制。


MongoDB的Primary节点选举基于心跳触发。一个复制集N个节点中的任意两个节点维持心跳,每个节点维护其他N-1个节点的状态。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210421004003308.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)



> 
> **心跳检测:** 整个集群需要保持一定的通信才能知道哪些节点活着哪些节点挂掉。mongodb节点会向副本集中的其他节点每2秒就会发送一次pings包,如果其他节点在10秒钟之内没有返回就标示为不能访问。每个节点内部都会维护一个状态映射表,表明当前每个节点是什么角色、日志时间戳等关键信息。如果主节点发现自己无法与大部分节点通讯则把自己降级为secondary只读节点。
> 
> 
> 


**主节点选举触发的时机:**



第一次初始化一个复制集
Secondary节点权重比Primary节点高时,发起替换选举
Secondary节点发现集群中没有Primary时,发起选举
Primary节点不能访问到大部分(Majority)成员时主动降级


**当触发选举时,Secondary节点尝试将自身选举为Primary。主节点选举是一个二阶段过程+多数派协议。**



第一阶段:
检测自身是否有被选举的资格 如果符合资格会向其它节点发起本节点是否有选举资格的
FreshnessCheck,进行同僚仲裁
第二阶段:
发起者向集群中存活节点发送Elect(选举)请求,仲裁者收到请求的节点会执行一系列合法性检查,如果检
查通过,则仲裁者(一个复制集中最多50个节点 其中只有7个具有投票权)给发起者投一票。
pv0通过30秒选举锁防止一次选举中两次投票。
pv1使用了terms(一个单调递增的选举计数器)来防止在一次选举中投两次票的情况。
多数派协议:
发起者如果获得超过半数的投票,则选举通过,自身成为Primary节点。获得低于半数选票的原因,除了常
见的网络问题外,相同优先级的节点同时通过第一阶段的同僚仲裁并进入第二阶段也是一个原因。因此,当
选票不足时,会sleep[0,1]秒内的随机时间,之后再次尝试选举。


#### 复制集搭建


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210421004612900.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)  
 **1.主节点配置 mongo\_37017.conf**



主节点配置

dbpath=/data/mongo/data/server1
bind_ip=0.0.0.0
port=37017
fork=true
logpath=/data/mongo/logs/server1.log
replSet=lagouCluster


**2.从节点1配置 mongo\_37018.conf**



dbpath=/data/mongo/data/server2
bind_ip=0.0.0.0
port=37018
fork=true
logpath=/data/mongo/logs/server2.log
replSet=lagouCluster


**3.从节点2配置 mongo\_37019.conf**



dbpath=/data/mongo/data/server3
bind_ip=0.0.0.0
port=37019
fork=true
logpath=/data/mongo/logs/server3.log
replSet=lagouCluster


**4.初始化节点配置**  
 启动三个节点 然后进入任意一个节点 运行如下命令:



var cfg ={“_id”:“lagouCluster”,
“protocolVersion” : 1,
“members”:[
{“_id”:1,“host”:“192.168.211.133:37017”,“priority”:10},
{“_id”:2,“host”:“192.168.211.133:37018”}
]
}
rs.initiate(cfg)
rs.status()


**5.节点的动态增删**



增加节点
rs.add(“192.168.211.133:37019”)
删除slave 节点
rs.remove(“192.168.211.133:37019”)


**6.复制集操作演示**  
 `进入主节点 ----- 插入数据 ------ 进入从节点验证`  
 **注意:默认节点下从节点不能读取数据。调用 rs.slaveOk() 解决**  
 为了保证高可用,在集群当中如果主节点挂掉后,会自动 在从节点中选举一个 重新做为主节点。



rs.status()


节点说明:


* PRIMARY 节点: 可以查询和新增数据
* SECONDARY 节点:只能查询 不能新增 基于priority 权重可以被选为主节点
* ARBITER 节点: 不能查询数据 和新增数据 ,不能变成主节点


#### 复制集成员的配置参数




| 参数字段 | 类型说明 | 取值 | 说明 |
| --- | --- | --- | --- |
| \_id | 整数 | \_id:0 | 复制集中的标示 |
| host | 字符串 | host:“主机:端口” | 节点主机名 |
| arbiterOnly | 布尔值 | arbiterOnly:true | 是否为仲裁(裁判)节点 |
| priority(权重) | 整数 | priority=0|1 | 默认1,是否有资格变成主节点,取值范围0-1000,0永远不会变成主节点 |
| hidden | 布尔值 | hidden=true|false,0|1 | 隐藏,权重必须为0,才可以设置 |
| votes | 整数 | votes= 0|1 | 投票,是否为投票节点,0 不投票,1投票 |
| slaveDelay | 整数 | slaveDelay=3600 | 从库的延迟多少秒 |
| buildIndexes | 布尔值 | buildIndexes=true|false,0|1 | 主库的索引,从库也创建,\_id索引无效 |



var cfg ={“_id”:“lagouCluster”,
“protocolVersion” : 1,
“members”:[
{“_id”:1,“host”:“192.168.211.133:37017”,“priority”:10},
{“_id”:2,“host”:“192.168.211.133:37018”,“priority”:0},
{“_id”:3,“host”:“192.168.211.133:37019”,“priority”:5},
{“_id”:4,“host”:“192.168.211.133:37020”,“arbiterOnly”:true}
]
};
// 重新装载配置,并重新生成集群节点。
rs.reconfig(cfg)
//重新查看集群状态
rs.status()


#### 有仲裁节点复制集搭建


和上面的配置步骤相同 只是增加了 一个特殊的仲裁节点;注入节点 执行



rs.addArb(“IP:端口”);
rs.addArb(“192.168.211.133:37020”)


### 分片集群


#### 什么是分片


分片(sharding)是MongoDB用来将大型集合水平分割到不同服务器(或者复制集)上所采用的方法。不需要功能强大的大型计算机就可以存储更多的数据,处理更大的负载。


#### 为什么要分片



1.存储容量需求超出单机磁盘容量。
2.活跃的数据集超出单机内存容量,导致很多请求都要从磁盘读取数据,影响性能。
3.IOPS超出单个MongoDB节点的服务能力,随着数据的增长,单机实例的瓶颈会越来越明显。
4.副本集具有节点数量限制。



> 
> 垂直扩展:增加更多的CPU和存储资源来扩展容量。  
>  水平扩展:将数据集分布在多个服务器上。水平扩展即分片。
> 
> 
> 


#### 分片的工作原理


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210422163152764.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)



> 
> 分片集群由以下3个服务组成:  
>  Shards Server: 每个shard由一个或多个mongod进程组成,用于存储数据。  
>  Router Server: 数据库集群的请求入口,所有请求都通过Router(mongos)进行协调,不需要在应用程  
>  序添加一个路由选择器,Router(mongos)就是一个请求分发中心它负责把应用程序的请求转发到对应的  
>  Shard服务器上。  
>  Config Server: 配置服务器。存储所有数据库元信息(路由、分片)的配置。
> 
> 
> 


**片键(shard key):** 为了在数据集合中分配文档,MongoDB使用分片主键分割集合。


**区块(chunk):** 在一个shard server内部,MongoDB还是会把数据分为chunks,每个chunk代表这个shard server内部一部分数据。MongoDB分割分片数据到区块,每一个区块包含基于分片主键的左闭右开的区间范围


**分片策略**


* 范围分片  
 范围分片是基于分片主键的值切分数据,每一个区块将会分配到一个范围。  
 范围分片适合满足在一定范围内的查找,例如查找X的值在[20,30)之间的数据,mongo 路由根据Config server中存储的元数据,可以直接定位到指定的shard的Chunk中。  
 缺点: 如果shard key有明显递增(或者递减)趋势,则新插入的文档多会分布到同一个chunk,无法扩展写的能力。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210422163355560.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)
* hash分片(Hash based sharding)  
 Hash分片是计算一个分片主键的hash值,每一个区块将分配一个范围的hash值。  
 Hash分片与范围分片互补,能将文档随机的分散到各个chunk,充分的扩展写能力,弥补了范围分片的不足,缺点是不能高效的服务范围查询,所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210422163439334.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)
* 组合片键 A + B(散列思想 不能是直接hash)  
 数据库中没有比较合适的片键供选择,或者是打算使用的片键基数太小(即变化少如星期只有7天可变化),可以选另一个字段使用组合片键,甚至可以添加冗余字段来组合。一般是粗粒度+细粒度进行组合。


无非从两个方面考虑,数据的查询和写入,最好的效果就是数据查询时能命中更少的分片,数据写入时能够随机的写入每个分片,关键在于如何权衡性能和负载。


#### 分片集群的搭建过程


![在这里插入图片描述](https://img-blog.csdnimg.cn/20210422163538763.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NTgxOTYx,size_16,color_FFFFFF,t_70)  
 **1.配置 并启动config 节点集群**  
 `节点1 config-17017.conf`



数据库文件位置

dbpath=config/config1
#日志文件位置
logpath=config/logs/config1.log

以追加方式写入日志

logappend=true

是否以守护进程方式运行

fork = true
bind_ip=0.0.0.0
port = 17017

表示是一个配置服务器

configsvr=true
#配置服务器副本集名称
replSet=configsvr


`节点2 config-17018.conf`



数据库文件位置

dbpath=config/config2
#日志文件位置
logpath=config/logs/config.log

以追加方式写入日志

logappend=true

是否以守护进程方式运行

fork = true
bind_ip=0.0.0.0
port = 17018

表示是一个配置服务器

configsvr=true
#配置服务器副本集名称
replSet=configsvr


`节点3 config-17019.conf`



数据库文件位置

dbpath=config/config3
#日志文件位置
logpath=config/logs/config3.log

以追加方式写入日志

logappend=true

是否以守护进程方式运行

fork = true
bind_ip=0.0.0.0
port = 17019

表示是一个配置服务器

configsvr=true
#配置服务器副本集名称
replSet=configsvr


`启动配置节点`



./bin/mongod -f config/config-17017.conf
./bin/mongod -f config/config-17018.conf
./bin/mongod -f config/config-17019.conf

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

# 数据库文件位置
dbpath=config/config1
#日志文件位置
logpath=config/logs/config1.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
port = 17017
# 表示是一个配置服务器
configsvr=true
#配置服务器副本集名称
replSet=configsvr

节点2 config-17018.conf

# 数据库文件位置
dbpath=config/config2
#日志文件位置
logpath=config/logs/config.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind\_ip=0.0.0.0
port = 17018
# 表示是一个配置服务器
configsvr=true
#配置服务器副本集名称
replSet=configsvr

节点3 config-17019.conf

# 数据库文件位置
dbpath=config/config3
#日志文件位置
logpath=config/logs/config3.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind\_ip=0.0.0.0
port = 17019
# 表示是一个配置服务器
configsvr=true
#配置服务器副本集名称
replSet=configsvr

启动配置节点

./bin/mongod -f config/config-17017.conf
./bin/mongod -f config/config-17018.conf
./bin/mongod -f config/config-17019.conf


[外链图片转存中...(img-VmWIzgMg-1715544482642)]
[外链图片转存中...(img-A5tZexbA-1715544482643)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值