mongodb
This commit is contained in:
parent
d96d356ecf
commit
355a9e6e86
@ -20,13 +20,11 @@ Docker 使用 Go 语言进行开发,基于 Linux 内核的 cgroup,namespace
|
||||
下图体现了 Docker 和传统虚拟化方式的不同之处:传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,再在该系统上运行所需应用进程;而 Docker 容器内的应用进程则是直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟,因此要比传统虚拟机更为轻便。
|
||||
|
||||
<div align="center"> <img src="../pictures/docker与虚拟机.png"/> </div>
|
||||
|
||||
## 二、Docker 架构与核心概念
|
||||
|
||||
Docker 使用 client-server 架构, Docker 客户端将命令发送给 Docker 守护进程,后者负责构建,运行和分发 Docker 容器。 Docker客户端和守护程序使用 REST API,通过 UNIX 套接字或网络接口进行通信。核心概念如下:
|
||||
|
||||
<div align="center"> <img src="../pictures/docker架构.png"/> </div>
|
||||
|
||||
### 2.1 镜像
|
||||
|
||||
Docker 镜像(Image)是一个特殊的文件系统,包含了程序运行时候所需要的资源和环境。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
|
||||
@ -43,7 +41,7 @@ Docker 镜像(Image)是一个特殊的文件系统,包含了程序运行
|
||||
|
||||
前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层称为 **容器存储层**。容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
|
||||
|
||||
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
|
||||
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡,因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
|
||||
|
||||
### 2.3 仓库
|
||||
|
||||
@ -62,7 +60,6 @@ Docker 客户端(docker)是用户与 Docker 交互的主要方式。当你
|
||||
Docker 提供了大量命令用于管理镜像、容器和服务,命令的统一使用格式为:` docker [OPTIONS] COMMAND` ,其中 OPTIONS 代表可选参数。需要注意的是 Docker 命令的执行一般都需要获取 root 权限,这是因为 Docker 的命令行工具 docker 与 docker daemon 是同一个二进制文件,docker daemon 负责接收并执行来自 docker 的命令,它的运行需要 root 权限。所有常用命令及其使用场景如下:
|
||||
|
||||
<div align="center"> <img src="../pictures/docker常用命令.jpg"/> </div>
|
||||
|
||||
### 3.1 基础命令
|
||||
|
||||
- **docker version**:用于查看 docker 的版本信息
|
||||
@ -278,7 +275,6 @@ docker run -it -p 8080:8080 spring-boot-base-java
|
||||
这里为了观察到启动效果,所以使用交互的方式启动,实际部署时可以使用`-d`参数来后台启动,输出如下:
|
||||
|
||||
<div align="center"> <img src="../pictures/dockerfile01.png"/> </div>
|
||||
|
||||
### 5.2 基于 JDK 镜像部署 Spring Boot 项目
|
||||
|
||||
上面的项目我们是基于最基础的 Centos 镜像开始构建,但由于 Docker Hub 上已经提供了 JDK 的镜像,我们也可以选择从 JDK 镜像开始构建,此时构建过程更加简单。构建步骤和上面的完全一致,只是 Dockerfile 的内容有所不同,如下:
|
||||
|
@ -30,7 +30,6 @@
|
||||
+ **mongos** :查询服务的路由器,它是集群对外提供服务的门户。mongos 从配置服务器获取数据块的相关信息,并将客户端请求路由到对应的分片服务器上。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-分片模式.png"/> </div>
|
||||
|
||||
### 1.2 分片键
|
||||
|
||||
为了将集合中的不同文档分发到不同的数据块,MongoDB 需要用户指定分片键,之后基于选定的分片策略将数据拆分到不同的数据块。每个需要分片的集合只能有一个分片键:
|
||||
@ -56,11 +55,9 @@
|
||||
无论采用何种分片策略,数据最终都被存储到对应范围的数据块 (chunk) 上,每个块默认的大小都是 64 M。由于数据源源不断的加入,当块超过指定大小时,就会进行块拆分。需要强调的是块拆分是一个轻量级的操作,因为在本质上并没有任何数据的移动,只是由 config servers 更新关于块的元数据信息。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-块拆分.png"/> </div>
|
||||
|
||||
当某个分片服务器上的数据过多时候,此时为了避免单服务器上 CPU 和 IO 操作的性能问题,就会发生块迁移,将块从一个分片迁移到另外一个分片,同时 config servers 也会更新相关块的元数据信息。 块迁移是由在后台运行的平衡器 (balancer) 所负责的,它在后台进行持续监控,如果最大和最小分片之间的块数量差异超过迁移阈值,平衡器则开始在群集中迁移块以确保数据的均匀分布。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-块迁移.png"/> </div>
|
||||
|
||||
块的大小是可以手动进行配置修改,但需要注意权衡利弊:
|
||||
|
||||
- 小块会导致频繁的数据拆分和迁移,从而致保证数据均匀的分布,但数据迁移会带来额外的网络开销,同时也会降低路由性能;
|
||||
@ -71,18 +68,14 @@
|
||||
这里需要注意块的迁移不会对查询造成任何影响,MongoDB 集群和 Redis 集群的查询原理不同。对于 Redis Cluster 而言,数据的散列规则同时也是查询时的路由规则。但是对于 MongoDB 分片集群而言,查询需要先经过 mongos ,mongos 会从 config servers 上获取块的位置信息和数据范围,然后按照这些信息进行匹配后再路由到正确的分片上。因此,从本质上而言 MongoDB 的分片策略和路由规则没有任何关系,假设按照分片策略将某文档分发到 Shard A 的 Chunk01 上,之后 Chunk01 迁移到 Shard B , 由于配置服务器会更新 Chunk01 块的位置信息,所以仍然能够正确路由到。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-路由查询.png"/> </div>
|
||||
|
||||
### 1.6 非分片集合
|
||||
|
||||
以上的所有讲解都是针对分片集合的,而在实际开发中并非每个集合都需要进行分片,MongoDB 允许在同一数据库下混合使用分片和非分片集合。每个数据库都有自己的主分片,所有非分片集合同一存储在主分片上。需要特别说明的是主分片和复本集中的主节点没有任何关系,在新数据库创建时程序会自动选择当前集群中最少数据量的分片作为主分片。如下图所示: Shard A 为主分片,集合 Collection1 是分片集合,而集合 Collection2 是非分片集合。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-非分片集合.png"/> </div>
|
||||
|
||||
## 二、集群搭建
|
||||
|
||||
这里我只有三台服务器,所以三台服务器上均部署 mongod 服务,形成两个分片副本集;同时三台服务器上均配置 config servers 服务,形成一个配置副本集。集群架构如下:
|
||||
|
||||
|
||||
这里我只有三台服务器,为保证高可用,三台服务器上均部署 mongod 服务(每个服务器上使用不同端口部署两个 mongod 服务),形成两个分片副本集;同时三台服务器上均部署 config servers 服务,形成一个配置副本集:
|
||||
|
||||
### 2.1 分片副本集配置
|
||||
|
||||
@ -285,7 +278,6 @@ db.runCommand({ addshard : "rs1/hadoop001:37018,hadoop002:37018,hadoop003:37018"
|
||||
务必注意,在添加分片副本集之前一定要切换到管理员角色,否则后面的添加操作会返回 `"ok" : 0` 的失败状态码,同时会提示 `addShard may only be run against the admin database.` 添加成功后,可以使用 `sh.status()` 查看集群状态,此时输出如下,可以看到两个分片副本集已经被成功添加。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-分片集群状态.png"/> </div>
|
||||
|
||||
### 2.8 测试分片
|
||||
|
||||
#### 1. 开启分片功能
|
||||
@ -323,7 +315,6 @@ db.users.insertMany(arr);
|
||||
插入数据完成后,执行 `sh.status()` 命令可以查看分片情况,以及块的数据信息,部分输出如下:
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-分片测试.png"/> </div>
|
||||
|
||||
## 参考资料
|
||||
|
||||
+ 官方文档:https://docs.mongodb.com/manual/sharding/
|
||||
|
@ -64,7 +64,7 @@
|
||||
db.collection.insert()
|
||||
```
|
||||
|
||||
在 MongoDB 3.2 之前,插入数据的语法如上,可以用于插入单条或者多条数据。在 3.2 之后,MongoDB 为了增强 API 的语义,增加了如下两个 API ,分别用于显示表达插入单条数据和多条数据的行为。
|
||||
在 MongoDB 3.2 之前,插入数据的语法如上,可以用于插入单条或者多条数据。在 3.2 之后,MongoDB 为了增强 API 的语义,增加了如下两个 API ,分别用于显示表达插入单条数据和多条数据的行为:
|
||||
|
||||
```shell
|
||||
db.collection.insertOne()
|
||||
@ -124,7 +124,7 @@ db.collection.find(<query>, <projection>)
|
||||
```
|
||||
|
||||
+ `<query>`:用于指定查询条件,不加任何条件则默认查询集合中全部数据;
|
||||
+ `<projection>`:可选操作,用于自定查询的返回字段,1 表示该字段包含在返回结果中,0 表示不返回,示例如下:
|
||||
+ `<projection>`:可选操作,用于过滤返回字段,1 表示对应的字段包含在返回结果中,0 表示不包含,示例如下:
|
||||
|
||||
```shell
|
||||
db.user.find({},{name: 1, ObjectId:-1})}
|
||||
@ -173,13 +173,13 @@ Mongodb 提供了逻辑操作符 `$or`、`$and`、`$not`、`$nor` ,用于处
|
||||
```shell
|
||||
db.user.find( { $or: [{ name: "heibai" }, { age: { $gt: 30 } }] })
|
||||
```
|
||||
查询所有姓名不是以 hei 开头的所有用户,此时可以使用 $not 操作符来配合正则表达式:
|
||||
查询姓名不是以 hei 开头的所有用户,此时可以使用 $not 操作符来配合正则表达式:
|
||||
|
||||
```shell
|
||||
db.user.find({name: {$not: /^hei*/}})
|
||||
```
|
||||
|
||||
如果文档中存在 name 字段,则它的值不能为 heibai,如果文档中存在 age 字段,则它的值不能大于 30 ,只有满足以上两个条件的文档才会被查询出来,示例如下:
|
||||
如果要求 name 字段的值不能为 heibai, age 字段的值不能大于 30,则对应的查询语句如下:
|
||||
|
||||
```shell
|
||||
db.user.find( { $nor: [{ name: "heibai" }, { age: { $gt: 30 } }] })
|
||||
@ -189,12 +189,12 @@ $and 操作符的使用率比较低,因为此时更好的方式是把多个条
|
||||
|
||||
### 3.5 集合查询
|
||||
|
||||
如果需要查询个人爱好中有 football 的所有用户,即只要集合 Hobby 中存在 football 即可,对应的查询方法如下:
|
||||
如果需要查询个人爱好中有 football 的所有用户,即集合 Hobby 中存在 football 即可,对应的查询语句如下:
|
||||
|
||||
```shell
|
||||
db.user.find({Hobby: "football"})
|
||||
```
|
||||
如果想要获取集合中指定位置等于指定值的文档,对应的查询方法如下:
|
||||
如果想要获取集合中指定位置等于指定值的文档,对应的查询语句如下:
|
||||
|
||||
```shell
|
||||
db.user.find({"Hobby.2": "football"})
|
||||
@ -279,7 +279,7 @@ db.user.updateOne(
|
||||
|
||||
### 4.3 数组修改器
|
||||
|
||||
在修改操作中,比较复杂的是对数组数据的修改,为了解决这个问题,MongoDB 提供了一系列的修改器,用于数组操作:
|
||||
在修改操作中,比较复杂的是对数组数据的修改,为了解决这个问题,MongoDB 提供了一系列的数组修改器,用于数组操作:
|
||||
|
||||
#### 1. $push
|
||||
|
||||
@ -316,7 +316,7 @@ db.user.updateOne(
|
||||
|
||||
#### 4. index
|
||||
|
||||
对于数组还可以直接修改指定下标位置的元素,示例如下:
|
||||
用于直接修改指定下标位置的元素,示例如下:
|
||||
|
||||
```shell
|
||||
db.user.updateOne(
|
||||
|
@ -25,7 +25,6 @@
|
||||
为保证数据安全,实现高可用,MongoDB 提供了复制功能,可以将主节点上的数据复制到多个从节点上,这样即便主节点异常,由于数据是以多副本的方式存储,仍然可以保证数据安全。一个标准的三节点的副本集的架构如下:
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-复制.png"/> </div>
|
||||
|
||||
#### 1. 初始同步
|
||||
|
||||
在副本集初始化时,主节点的 mongod 进程会扫描当前节点上每个数据库中的每个集合,然后将这些数据发送给从节点,进行初始化的全量复制。
|
||||
@ -45,7 +44,6 @@ MongoDB 按 namespace 或 document id 对每批操作进行分组,并使用不
|
||||
虽然仲裁者可以占用更少的服务器资源,但是由于其并不存储数据,所以对数据的安全性并不能起到帮助作用。因此应该尽量避免使用仲裁者,同时尽量保证最多只使用一个仲裁者,即如果节点数量恰好是偶数,则添加一个仲裁者,如果节点数量是奇数,那就不需要仲裁者。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-仲裁者.png"/> </div>
|
||||
|
||||
## 二、故障发现与恢复
|
||||
|
||||
### 2.1 故障发现
|
||||
@ -59,14 +57,13 @@ MongoDB 的选举算法会尝试让高优先级的节点优先发起选举,从
|
||||
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-故障恢复.png"/> </div>
|
||||
|
||||
### 2.3 投票成员
|
||||
|
||||
节点发起选举后,需要具有投票权的节点进行投票,当获得半数以上选票时,该备用节点可以成为新的主节点。对于一个复制集,只有处于以下状态的节点拥有投票权,这些节点统称为投票成员:
|
||||
|
||||
- **PRIMARY**:副本集的主节点。
|
||||
- **SECONDARY**:处于复制状态的从节点。
|
||||
- **STARTUP2**:mongod 完成配置加载后,副本集的每个成员都进入 STARTUP2 状态,此时它成为副本集的活动成员并且有资格投票。然后该成员决定是否进行初始同步。如果成员开始初始同步,则成员将保留在 STARTUP2 状态,直到所有数据复制完成并构建好索引。之后成员过渡到RECOVERING。
|
||||
- **STARTUP2**:mongod 完成配置加载后,副本集的每个成员都进入 STARTUP2 状态,此时它成为副本集的活动成员并且有资格投票。然后该成员决定是否进行初始同步。如果成员开始初始同步,则成员将保留在 STARTUP2 状态,直到所有数据复制完成并构建好索引。之后成员过渡到 RECOVERING。
|
||||
- **RECOVERING**:当副本集的成员尚未准备好接受读取操作时,它将进入 RECOVERING 状态。处于恢复状态的成员有资格在选举中投票,但没有资格成为主节点。
|
||||
- **ARBITER**:ARBITER 状态的成员不复制数据或接受写入操作,仲裁者通常处于这一状态。
|
||||
- **ROLLBACK**:如果成员正在进行数据回滚,它就处于 ROLLBACK 状态。
|
||||
@ -79,7 +76,6 @@ MongoDB 的选举算法会尝试让高优先级的节点优先发起选举,从
|
||||
如下示例是一个 9 个成员的副本集,包含 7 个投票成员和 2 个无投票成员:
|
||||
|
||||
<div align="center"> <img src="../pictures/mongdb-vote.png"/> </div>
|
||||
|
||||
## 三、搭建副本集
|
||||
|
||||
这里以搭建一个三节点的副本集为例,使用三台服务器,主机名分别为 hadoop001,hadoop002,hadoop003。
|
||||
@ -89,7 +85,6 @@ MongoDB 的选举算法会尝试让高优先级的节点优先发起选举,从
|
||||
选择所需版本的 MongoDB 进行下载,下载地址为: https://www.mongodb.com/download-center/community
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-version-select.png"/> </div>
|
||||
|
||||
这里我下载的版本为 `4.0.10` , 安装环境为 `RHEL 7.0`,下载后进行解压:
|
||||
|
||||
```shell
|
||||
@ -181,7 +176,6 @@ rs.initiate( {
|
||||
使用 `rs.status()` 命令查看副本集状态,部分输出如下。从输出中可以看到 hadoop001 为 PRIMARY 节点,而 hadoop002 和 hadoop003 均为 SECONDARY 节点,此时代表副本集已经搭建成功。
|
||||
|
||||
<div align="center"> <img src="../pictures/mongodb-副本集状态.png"/> </div>
|
||||
|
||||
## 参考资料
|
||||
|
||||
- 官方文档:https://docs.mongodb.com/manual/replication/
|
||||
|
@ -75,7 +75,7 @@ db.user.createIndex( { name: -1 } )
|
||||
db.user.getIndexes()
|
||||
```
|
||||
|
||||
从输出中可以看到默认的索引名为:字段名+排序规则。这里除了我们为 name 字段创建的索引外,集合中还有一个 `_id` 字段的索引,这是程序自动创建的,用于禁止插入相同 `_id` 的文档。
|
||||
从输出中可以看到默认的索引名为:字段名+排序规则。这里除了我们为 name 字段创建的索引外,集合中还有一个 `_id` 字段的索引,这是程序自动创建的,用于禁止插入相同 `_id` 的文档:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -110,11 +110,10 @@ db.user.find({}).sort({name:-1})
|
||||
db.user.find({}).sort({name:1})
|
||||
```
|
||||
|
||||
当前大多数数据库都支持双向遍历索引,这和存储结构有关 (如下图)。在 B-Tree 结构的叶子节点上,存储了索引键的值及其对应文档的位置信息,而每个叶子节点间则类似于双向链表,所以如下图既可以从值为 4 的索引遍历到值为 92 的索引,反之亦然。
|
||||
当前大多数数据库都支持双向遍历索引,这和存储结构有关 (如下图)。在 B-Tree 结构的叶子节点上,存储了索引键的值及其对应文档的位置信息,而每个叶子节点间则类似于双向链表,既可以从前往后遍历,也可以从后往前遍历:
|
||||
|
||||
<div align="center"> <img src="../pictures/b-tree.png"/> </div>
|
||||
|
||||
|
||||
### 2.2 复合索引
|
||||
|
||||
支持为多个字段创建索引,示例如下:
|
||||
@ -123,7 +122,7 @@ db.user.find({}).sort({name:1})
|
||||
db.user.createIndex( { name: -1,birthday: 1} )
|
||||
```
|
||||
|
||||
需要注意的是 MongoDB 的复合索引具备前缀索引的特征,即如果你创建了索引 `{ a:1, b: 1, c: 1, d: 1 }`,那么等价于在该集合上,还存在了以下三个索引,这三个隐式索引同样可以用于优化查询和排序操作:
|
||||
需要注意的是 MongoDB 的复合索引具备前缀索引的特征,即如果你创建了索引 `{ a:1, b: 1, c: 1, d: 1 }`,那么等价于在该集合上还存在了以下三个索引,这三个隐式索引同样可以用于优化查询和排序操作:
|
||||
|
||||
```shell
|
||||
{ a: 1 }
|
||||
@ -202,7 +201,7 @@ db.<collection>.createIndex(
|
||||
db.user.createIndex( { name: -1,birthday: 1}, { unique: true })
|
||||
```
|
||||
|
||||
此时再执行下面的操作就会报错,因为 name = heibai 并且 birthday = new Date(1998,08,23) 的数据已经存在。
|
||||
此时再执行下面的操作就会报错,因为 `name = heibai` 并且 `birthday = new Date(1998,08,23)` 的数据已经存在:
|
||||
|
||||
```shell
|
||||
db.user.insertOne({
|
||||
@ -211,7 +210,7 @@ db.user.insertOne({
|
||||
})
|
||||
```
|
||||
|
||||
上面这种情况比较明显,但是如果你执行下面这个操作两次,你会发现只有第一次能够插入成功,第二个就会报 duplicate key 异常。这是因为在唯一索引的约束下,不存在这种状态也会被当做一种唯一值。
|
||||
上面这种情况比较明显,但是如果你执行下面这个操作两次,你会发现只有第一次能够插入成功,第二个就会报 duplicate key 异常。这是因为在唯一索引的约束下,name 不存在的这种状态也会被当做一种唯一状态:
|
||||
|
||||
```shell
|
||||
db.user.insertOne({
|
||||
@ -234,10 +233,10 @@ db.user.createIndex( { name: -1,birthday: 1}, { unique: true,sparse: true})
|
||||
|
||||
### 3.3 部分索引
|
||||
|
||||
部分索引主要用于为符合条件的部分数据创建索引,它必须与 partialFilterExpression 选项一起使用。 partialFilterExpression 选项可以使用以下表达式来确定数据范围:
|
||||
部分索引主要用于为符合条件的部分数据创建索引,它必须与 `partialFilterExpression` 选项一起使用。 `partialFilterExpression` 选项可以使用以下表达式来确定数据范围:
|
||||
|
||||
- 等式表达式(即 字段: 值 或使用 $eq 运算符);
|
||||
- $exists: true 表达式;
|
||||
- 等式表达式(即 `字段: 值` 或使用 $eq 运算符);
|
||||
- `$exists: true` 表达式;
|
||||
- $gt、$gte、$lt、$lte 操作符;
|
||||
- $type 操作符;
|
||||
- 处于顶层的 $and 操作符。
|
||||
@ -253,7 +252,7 @@ db.user.createIndex(
|
||||
|
||||
### 3.4 TTL 索引
|
||||
|
||||
TTL 索引允许为每个文档设置一个超时时间,当一个文档达到超时时间后,就会被删除。TTL索引的到期时间等于索引字段的值 + 指定的秒数,示例如下:
|
||||
TTL 索引允许为每个文档设置一个超时时间,当一个文档达到超时时间后,就会被删除。TTL索引的到期时间等于索引字段的值 + 指定的秒数,这里的索引字段的值只能是 Date 类型,示例如下:
|
||||
|
||||
```shell
|
||||
db.user.createIndex( { "birthday": 1 }, { expireAfterSeconds: 60 } )
|
||||
@ -328,17 +327,17 @@ db.user.find({name:"heibai"},{name:1,age:1}).sort({ name:1}).explain()
|
||||
}
|
||||
```
|
||||
|
||||
输出结果中内层的 inputStage.stage 的值为 `IXSCAN`,代表此时用到了索引进行扫描,并且 indexName 字段显示了对应的索引为 name_-1_birthday_1。而外层 inputStage.stage 的值为 `FETCH`,代表除了从索引上获取数据外,还需要去对应的文档上获取数据,因为 age 信息并不存储在索引上。这个输出可以证明 MongoDB 是支持前缀索引的,且单键索引支持双向扫描。
|
||||
输出结果中内层的 `inputStage.stage` 的值为 `IXSCAN`,代表此时用到了索引进行扫描,并且 `indexName` 字段显示了对应的索引为 `name_-1_birthday_1`。而外层 `inputStage.stage` 的值为 `FETCH`,代表除了从索引上获取数据外,还需要去对应的文档上获取数据,因为 age 信息并不存储在索引上。这个输出可以证明 MongoDB 是支持前缀索引的,且单键索引支持双向扫描。
|
||||
|
||||
### 5.2 覆盖索引
|
||||
|
||||
这里我们对上面的查询语句略做修改,不返回 age 字段和默认的 _id 字段,语句如下:
|
||||
这里我们对上面的查询语句略做修改,不返回 age 字段和默认的 `_id` 字段,语句如下:
|
||||
|
||||
```shell
|
||||
db.user.find({name:"heibai"},{_id:0, name:1}).sort({ name:1 }).explain()
|
||||
```
|
||||
|
||||
此时输出结果如下。可以看到该查询少了一个 `FETCH` 阶段。代表此时只需要扫描索引就可以获取到所需的全部信息,这种情况下 name_-1_birthday_1 索引就是这一次查询操作的覆盖索引。
|
||||
此时输出结果如下。可以看到该查询少了一个 `FETCH` 阶段。代表此时只需要扫描索引就可以获取到所需的全部信息,这种情况下 `name_-1_birthday_1` 索引就是这一次查询操作的覆盖索引。
|
||||
|
||||
```json
|
||||
"inputStage" : {
|
||||
|
@ -61,7 +61,7 @@ db.employees.insertMany([
|
||||
一个简单的聚合操作如下,这个聚合操作会经过两个阶段的数据处理:
|
||||
|
||||
+ 第一个管道阶段为 $match:会筛选出所有性别值为 F 的雇员的文档,然后输出到下一个管道操作中;
|
||||
+ 第二个管道阶段为 $project:用于定义返回的字段内容,这里返回 fullname 字段,它由 firstName + lastName 组成。
|
||||
+ 第二个管道阶段为 $project:用于定义返回的字段内容,这里返回 fullname 字段,它由 `firstName + lastName` 组成。
|
||||
|
||||
```shell
|
||||
db.employees.aggregate([
|
||||
@ -116,7 +116,7 @@ db.employees.aggregate([
|
||||
])
|
||||
```
|
||||
|
||||
从 MongoDB 3.6 开始,还可以在聚合表达式中使用 $project + 变量 REMOVE 来按照条件定义返回字段,设置为 REMOVE 变量的字段将会从 $projection 的输出中排除。示例如下:
|
||||
从 MongoDB 3.6 开始,还可以在聚合表达式中使用 `$project + REMOVE` 来按照条件过滤返回字段,设置为 REMOVE 的字段将会从 $projection 的输出中排除。示例如下:
|
||||
|
||||
```shell
|
||||
db.employees.aggregate([
|
||||
@ -228,7 +228,7 @@ db.employees.aggregate( [
|
||||
] )
|
||||
```
|
||||
|
||||
此时输出内容如下。如果 preserveNullAndEmptyArrays 的值为 false 或者没有设置,则 10003 这条数据不会被输出。
|
||||
此时输出内容如下。如果 preserveNullAndEmptyArrays 的值为 false 或者没有设置,则 10003 这条数据不会被输出:
|
||||
|
||||
```json
|
||||
{"emp_no":10001,"hobby":"basketball","arrayIndex":0},
|
||||
@ -245,9 +245,7 @@ db.employees.aggregate( [
|
||||
|
||||
### 1.5 $sort
|
||||
|
||||
$sort 主要用于排序操作,需要注意的是如果可以,应当尽量将该操作放置在管道的第一阶段,从而可以利用索引进行排序,否则就需要使用内存进行排序,这时排序操作就会变得相当昂贵,需要额外消耗内存和计算资源。
|
||||
|
||||
示例如下:
|
||||
$sort 主要用于排序操作,需要注意的是如果可以,应当尽量将该操作放置在管道的第一阶段,从而可以利用索引进行排序,否则就需要使用内存进行排序,这时排序操作就会变得相当昂贵,需要额外消耗内存和计算资源。示例如下:
|
||||
|
||||
```shell
|
||||
db.employees.aggregate([
|
||||
@ -331,7 +329,7 @@ db.employees.aggregate([
|
||||
])
|
||||
```
|
||||
|
||||
输出结果如下,为节省篇幅和突出重点,这里只显示部分内容,下文亦同。从输出中可以看到员工的职位信息都作为内嵌文档插入到指定的 emp_title 字段下,等价于执行了 left outer join 操作。
|
||||
输出结果如下,为节省篇幅和突出重点,这里只显示部分内容,下文亦同。从输出中可以看到员工的职位信息都作为内嵌文档插入到指定的 emp_title 字段下,等价于执行了 left outer join 操作:
|
||||
|
||||
```text
|
||||
{
|
||||
@ -384,7 +382,7 @@ db.employees.aggregate([
|
||||
}
|
||||
```
|
||||
|
||||
- **from**:指定同一数据库中的集合以进行连接操作;
|
||||
- **from**:指定同一数据库中的集合以进行连接操作。
|
||||
- **let**:可选操作。用于指定在管道阶段中使用的变量,需要注意的是访问 let 变量必须使用 $expr 运算符。
|
||||
- **pipeline**:必选值,用于指定要在已连接集合上运行的管道。需要注意的是该管道无法直接访问输入文档中的字段,需要先在 let 中进行定义,然后再引用。
|
||||
- **as**:指定用于存放匹配文档的新数组字段的名称。如果指定的字段已存在,则进行覆盖。
|
||||
@ -526,7 +524,7 @@ $out 用于将数据写入指定的集合,它必须是管道中的最后一个
|
||||
- 创建临时集合;
|
||||
- 将索引从现有集合复制到临时集合;
|
||||
- 将文档插入临时集合中;
|
||||
- 调用 db.collection.renameCollection(target, true) 方法将临时集合重命名为目标集合。
|
||||
- 调用 `db.collection.renameCollection(target, true)` 方法将临时集合重命名为目标集合。
|
||||
|
||||
$out 的使用示例如下:
|
||||
|
||||
@ -554,13 +552,13 @@ db.employees.aggregate([
|
||||
|
||||
#### $sort + $limit
|
||||
|
||||
当排序操作在限制操作之前时,如果没有中间阶段会修改文档数量 (例如 $unwind,$group),优化器会将 $limit 合并到 $sort中。如果在 $sort 和 $limit 之间存在修改文档数量的管道阶段,MongoDB 将不会执行合并。
|
||||
当排序操作在限制操作之前时,如果没有中间阶段会修改文档数量 (例如 $unwind,$group),优化器会将 $limit 合并到 $sort 中。如果在 $sort 和 $limit 之间存在修改文档数量的管道阶段,MongoDB 将不会执行合并。
|
||||
|
||||
了解这些优化策略可以有助于我们在开发中合理设置管道的顺序。想要了解全部的优化策略,可以参阅 MongoDB 的官方文档:[Aggregation Pipeline Optimization](https://docs.mongodb.com/manual/core/aggregation-pipeline-optimization/) 。
|
||||
|
||||
## 三、MapReduce
|
||||
|
||||
MongoDB 的 MapReduce 在概念上与 Hadoop MapReduce 类似,MongoDB 将 *map* 阶段应用于每个符合条件的每个输入文档,*map* 阶段的输出结果是 key-value 键值对。对于具有多个值的 key,MongoDB 会执行 *reduce* 阶段,该阶段负责收集并聚合数据,然后将结果存储在集合中。
|
||||
MongoDB 的 MapReduce 在概念上与 Hadoop MapReduce 类似,MongoDB 将 *map* 阶段应用于每个符合条件的输入文档,*map* 阶段的输出结果是 key-value 键值对。对于具有多个值的 key,MongoDB 会执行 *reduce* 阶段,该阶段负责收集并聚合数据,然后将结果存储在集合中。
|
||||
|
||||
这里我们以按照性别分组计算员工的平均年龄为例,演示 MapReduce 的基本使用,语句如下:
|
||||
|
||||
@ -605,7 +603,7 @@ db.employees.count({gender:"M"})
|
||||
|
||||
### 4.2 estimatedDocumentCount
|
||||
|
||||
官方更加推荐使用 estimatedDocumentCount 计算集合中所有文档的数量,它会直接获取元数据信息并返回,因此它并不支持传入查询条件,示例如下:
|
||||
官方更加推荐使用 estimatedDocumentCount 计算集合中所有文档的数量,它会直接获取元数据信息并返回,因此它不支持传入查询条件,示例如下:
|
||||
|
||||
```shell
|
||||
db.employees.estimatedDocumentCount({})
|
||||
|
@ -40,7 +40,6 @@ Nginx 能够同时支持正向代理和反向代理,这两种代理模式的
|
||||
+ 反向代理发生在服务端,客户端并不知道发生了代理,示例如下。用户只知道将请求发送给 Nginx,但是并不知道请求被转发了,也不知道被转发给了哪一台应用服务器。实际上对于用户来说,他也没必要知道,因为请求结果都是相同的。
|
||||
|
||||
<div align="center"> <img src="../pictures/nginx-plus.png"/> </div>
|
||||
|
||||
## 二、基本命令
|
||||
|
||||
Nginx Shell 的基本使用格式如下:
|
||||
@ -55,8 +54,8 @@ nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
|
||||
- **-t**:检测配置文件是否有语法错误;
|
||||
- **-q**:静默模式,在检测配置期间除了错误提示外,不输出其他消息;
|
||||
- **-s signal**:向 Master 进程发送信号,支持以下信号类型:stop ( 立即停止 ),quit ( 优雅停止 ),reload ( 重新加载配置文件 ),reopen (打开一个新的日志文件来继续记录日志) ;
|
||||
- **-p prefix** :指定路径的前缀,默认为安装目录,如 /usr/app/nginx-1.16.1/ ;
|
||||
- **-c filename** :指定配置文件的位置, 默认值为 conf/nginx.conf,其实际指向的路径为 prefix + filename;
|
||||
- **-p prefix** :指定路径的前缀,默认为安装目录,如 `/usr/app/nginx-1.16.1/` ;
|
||||
- **-c filename** :指定配置文件的位置, 默认值为 `conf/nginx.conf`,其实际指向的路径为 `prefix + filename`;
|
||||
- **-g directives**:从指定的配置文件中设置全局指令。
|
||||
|
||||
## 三、配置格式
|
||||
@ -73,9 +72,9 @@ Nginx 的配置由全局配置和局部配置(指令块)共同组成,所
|
||||
|
||||
指令块使用大括号进行划分,大括号内是独立的配置上下文,包含指令和具体的参数,每行指令以分号作为结尾。除此之外,Nginx 的配置中还支持以下操作:
|
||||
|
||||
+ 使用 include 语法来引入外部配置文件,从而可以将每个独立的配置拆分到单独的文件中;
|
||||
+ 支持使用 # 符号来添加注释;
|
||||
+ 支持使用 $ 符号来引用变量;
|
||||
+ 支持使用 `include` 语法来引入外部配置文件,从而可以将每个独立的配置拆分到单独的文件中;
|
||||
+ 支持使用 `#` 符号来添加注释;
|
||||
+ 支持使用 `$` 符号来引用变量;
|
||||
+ 部分指令的参数支持使用正则表达式。
|
||||
|
||||
### 3.2 时间和空间单位
|
||||
@ -216,7 +215,7 @@ Nginx 通常用作 HTTP 服务器来部署静态资源,其具体的操作步
|
||||
|
||||
### 4.1 增加配置
|
||||
|
||||
修改 nginx.conf ,并在 http 指令块中增加如下配置:
|
||||
修改 `nginx.conf` ,并在 http 指令块中增加如下配置:
|
||||
|
||||
```properties
|
||||
server {
|
||||
@ -290,7 +289,7 @@ run -d -it --privileged=true -v /usr/webapps02:/usr/local/tomcat/webapps \
|
||||
|
||||
### 5.2 负载均衡配置
|
||||
|
||||
修改 nginx.conf ,并在 http 指令块中增加如下配置:
|
||||
修改 `nginx.conf` ,并在 http 指令块中增加如下配置:
|
||||
|
||||
```properties
|
||||
# 这里指令块的名称可以随意指定,只要和下面的proxy_pass的值相同即可,通常配置为项目名
|
||||
@ -389,7 +388,7 @@ server {
|
||||
|
||||
#### 1. No such file or directory
|
||||
|
||||
第一个常见的问题是找不到静态资源,此时可以查看 logs 目录下的 error.log 日志,通常输出如下:
|
||||
第一个常见的问题是找不到静态资源,此时可以查看 logs 目录下的 `error.log` 日志,通常输出如下:
|
||||
|
||||
```shell
|
||||
2019/09/01 17:12:43 [error] 12402#0: *163 open() "/usr/resources/spring-boot-tomcat/css/show.css"
|
||||
@ -398,7 +397,7 @@ request: "GET /spring-boot-tomcat/css/show.css HTTP/1.1", host: "192.168.0.226:9
|
||||
referrer: "http://192.168.0.226:9020/spring-boot-tomcat/index"
|
||||
```
|
||||
|
||||
出现这个问题,是因为 Nginx 要求静态资源的请求路径必须和原有请求路径完全相同,这里我的项目在 Tomcat 中解压后的项目名为 pring-boot-tomcat,以 show.css 文件为例,其正确的存储路径应该为:
|
||||
出现这个问题,是因为 Nginx 要求静态资源的请求路径必须和原有请求路径完全相同,这里我的项目在 Tomcat 中解压后的项目名为 `pring-boot-tomcat`,以 show.css 文件为例,其正确的存储路径应该为:
|
||||
|
||||
```shell
|
||||
/usr/resources/spring-boot-tomcat/css/show.css
|
||||
|
Loading…
x
Reference in New Issue
Block a user