diff --git a/README.md b/README.md
index 8038b6a..4189d9d 100644
--- a/README.md
+++ b/README.md
@@ -22,34 +22,34 @@
### MySQL
-+ [MySQL 核心概念](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MySQL_基础.md)
-+ [MySQL 备份详解](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MySQL_备份.md)
-+ [MySQL 二进制日志复制、GTID 复制与半同步复制](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MySQL_复制.md)
-+ [MySQL 高可用架构之 PXC 集群](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MySQL_PXC集群.md)
-+ [MyCat 读写分离与分库分表](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MySQL_Mycat中间件.md)
-+ [MySQL 查询性能分析之 Explain](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MySQL_EXPLAIN.md)
++ [MySQL 核心概念](notes/MySQL_基础.md)
++ [MySQL 备份详解](notes/MySQL_备份.md)
++ [MySQL 二进制日志复制、GTID 复制与半同步复制](notes/MySQL_复制.md)
++ [MySQL 高可用架构之 PXC 集群](notes/MySQL_PXC集群.md)
++ [MyCat 读写分离与分库分表](notes/MySQL_Mycat中间件.md)
++ [MySQL 查询性能分析之 Explain](notes/MySQL_EXPLAIN.md)
### Redis
-+ [Redis 基本数据类型和常用命令](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Redis_数据类型和常用命令.md)
-+ [Redis AOF 和 RDB 持久化策略原理](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Redis_持久化.md)
-+ [Redis 哨兵模式](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Redis_哨兵模式.md)
-+ [Reids 集群模式](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Redis_集群模式.md)
++ [Redis 基本数据类型和常用命令](notes/Redis_数据类型和常用命令.md)
++ [Redis AOF 和 RDB 持久化策略原理](notes/Redis_持久化.md)
++ [Redis 哨兵模式](notes/Redis_哨兵模式.md)
++ [Reids 集群模式](notes/Redis_集群模式.md)
### MongoDB
-+ [MongoDB 基础](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_基础.md)
-+ [MongoDB 索引](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_索引.md)
-+ [MongoDB 聚合](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_聚合.md)
-+ [MongoDB 复制](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_复制.md)
-+ [MongoDB 分片](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_分片.md)
++ [MongoDB 基础](notes/MongoDB_基础.md)
++ [MongoDB 索引](notes/MongoDB_索引.md)
++ [MongoDB 聚合](notes/MongoDB_聚合.md)
++ [MongoDB 复制](notes/MongoDB_复制.md)
++ [MongoDB 分片](notes/MongoDB_分片.md)
## 📟 操作系统
-+ [Linux 常用 Shell 命令](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Linux_常用Shell命令.md)
-+ [Docker 基础](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Docker_基础.md)
++ [Linux 常用 Shell 命令](notes/Linux_常用Shell命令.md)
++ [Docker 基础](notes/Docker_基础.md)
@@ -91,9 +91,9 @@
### RabbitMQ
-- [RabbitMQ 核心概念](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/RabbitMQ_基础.md)
-- [RabbitMQ 客户端开发](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/RabbitMQ_客户端开发.md)
-- [基于 HAProxy + KeepAlived 搭建 RabbitMQ 高可用集群](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/RabbitMQ_高可用集群架构.md)
+- [RabbitMQ 核心概念](notes/RabbitMQ_基础.md)
+- [RabbitMQ 客户端开发](notes/RabbitMQ_客户端开发.md)
+- [基于 HAProxy + KeepAlived 搭建 RabbitMQ 高可用集群](notes/RabbitMQ_高可用集群架构.md)
### ZooKeeper
@@ -105,7 +105,7 @@
### Nginx
-- [Nginx 基础之静态网站部署,负载均衡,动静分离](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/Nginx_基础.md)
+- [Nginx 基础之静态网站部署,负载均衡,动静分离](notes/Nginx_基础.md)
diff --git a/code/Java/java-concurrency/pom.xml b/code/Java/java-concurrency/pom.xml
new file mode 100644
index 0000000..f30b2d2
--- /dev/null
+++ b/code/Java/java-concurrency/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+ com.heibaiying
+ java-concurrency
+ 1.0
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J1_Interrupt.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J1_Interrupt.java
new file mode 100644
index 0000000..f5ca8ca
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J1_Interrupt.java
@@ -0,0 +1,17 @@
+package com.heibaiying.interrupt;
+
+/**
+ * interrupt() 只是设置中断标志位,并不能中断线程
+ */
+public class J1_Interrupt {
+ public static void main(String[] args) throws InterruptedException {
+ Thread thread = new Thread(() -> {
+ while (true) {
+ System.out.println("子线程打印");
+ }
+ });
+ thread.start();
+ Thread.sleep(10);
+ thread.interrupt();
+ }
+}
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J2_IsInterrupted.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J2_IsInterrupted.java
new file mode 100644
index 0000000..0e027e4
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J2_IsInterrupted.java
@@ -0,0 +1,18 @@
+package com.heibaiying.interrupt;
+
+/**
+ * isInterrupted() 用于检查当前线程是否存在中断标志位
+ */
+public class J2_IsInterrupted {
+
+ public static void main(String[] args) throws InterruptedException {
+ Thread thread = new Thread(() -> {
+ while (!Thread.currentThread().isInterrupted()) {
+ System.out.println("子线程打印");
+ }
+ });
+ thread.start();
+ Thread.sleep(10);
+ thread.interrupt();
+ }
+}
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J3_Interrupted.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J3_Interrupted.java
new file mode 100644
index 0000000..bae1495
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/interrupt/J3_Interrupted.java
@@ -0,0 +1,18 @@
+package com.heibaiying.interrupt;
+
+/**
+ * 判断当前线程的中断状态,并清除当前线程的中断标志位
+ */
+public class J3_Interrupted {
+
+ public static void main(String[] args) throws InterruptedException {
+ Thread thread = new Thread(() -> {
+ while (!Thread.interrupted() || !Thread.currentThread().isInterrupted()) {
+ System.out.println("子线程打印");
+ }
+ });
+ thread.start();
+ Thread.sleep(10);
+ thread.interrupt();
+ }
+}
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J1_Normal.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J1_Normal.java
new file mode 100644
index 0000000..07f3ef5
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J1_Normal.java
@@ -0,0 +1,22 @@
+package com.heibaiying.waitAndNotify;
+
+/**
+ * 正常情况下
+ */
+public class J1_Normal {
+
+ private static final Object object = new Object();
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (object) {
+ System.out.println("线程1");
+ }
+ }).start();
+ new Thread(() -> {
+ synchronized (object) {
+ System.out.println("线程2");
+ }
+ }).start();
+ }
+}
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J2_KeepObjectLocked.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J2_KeepObjectLocked.java
new file mode 100644
index 0000000..dbed016
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J2_KeepObjectLocked.java
@@ -0,0 +1,29 @@
+package com.heibaiying.waitAndNotify;
+
+/**
+ * 锁住对象不释放
+ */
+public class J2_KeepObjectLocked {
+
+ private static final Object object = new Object();
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (object) {
+ while (true) {
+ try {
+ Thread.sleep(1000);
+ System.out.println("线程1");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }).start();
+ new Thread(() -> {
+ synchronized (object) {
+ System.out.println("线程2");
+ }
+ }).start();
+ }
+}
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J3_WaitAndNotify.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J3_WaitAndNotify.java
new file mode 100644
index 0000000..d28b183
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J3_WaitAndNotify.java
@@ -0,0 +1,31 @@
+package com.heibaiying.waitAndNotify;
+
+/**
+ * 等待与唤醒
+ */
+public class J3_WaitAndNotify {
+
+ private static final Object object = new Object();
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (object) {
+ try {
+ System.out.println("对象object等待");
+ object.wait();
+ System.out.println("线程1后续操作");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ new Thread(() -> {
+ synchronized (object) {
+ System.out.println("对象object唤醒");
+ object.notify();
+ System.out.println("线程2后续操作");
+ }
+ }).start();
+ }
+}
+
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J4_AlternatePrinting.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J4_AlternatePrinting.java
new file mode 100644
index 0000000..3a5dc7c
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J4_AlternatePrinting.java
@@ -0,0 +1,52 @@
+package com.heibaiying.waitAndNotify;
+
+/**
+ * 利用 wait 和 Notify 实现交替打印
+ */
+public class J4_AlternatePrinting {
+
+ private static final Object object = new Object();
+ private volatile static boolean flag = false;
+ private static int i = 0;
+ private static int threshold = 1000000;
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (object) {
+ try {
+ while (i <= threshold) {
+ if (flag) {
+ object.wait();
+ } else {
+ object.notify();
+ System.out.println("Thread 1 : " + i++);
+ flag = !flag;
+ }
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ new Thread(() -> {
+ synchronized (object) {
+ while (i <= threshold) {
+ try {
+ while (i <= threshold) {
+ if (flag) {
+ object.notify();
+ System.out.println("Thread 2 : " + i++);
+ flag = !flag;
+ } else {
+ object.wait();
+ }
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }).start();
+ }
+}
+
diff --git a/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J5_NotifyAll.java b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J5_NotifyAll.java
new file mode 100644
index 0000000..2f818d5
--- /dev/null
+++ b/code/Java/java-concurrency/src/main/java/com/heibaiying/waitAndNotify/J5_NotifyAll.java
@@ -0,0 +1,43 @@
+package com.heibaiying.waitAndNotify;
+
+/**
+ * 全部唤醒
+ */
+public class J5_NotifyAll {
+
+ private static final Object object = new Object();
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (object) {
+ try {
+ System.out.println("对象object在线程1等待");
+ object.wait();
+ System.out.println("线程1后续操作");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ new Thread(() -> {
+ synchronized (object) {
+ try {
+ System.out.println("对象object在线程2等待");
+ object.wait();
+ System.out.println("线程2后续操作");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ new Thread(() -> {
+ synchronized (object) {
+ System.out.println("对象object唤醒");
+ // 如果是object.notify()则是随机唤醒任意一个等待
+ object.notifyAll();
+ System.out.println("线程3后续操作");
+ }
+ }).start();
+ }
+}
+
diff --git a/notes/Docker_基础.md b/notes/Docker_基础.md
index 34ddfaa..40a4503 100644
--- a/notes/Docker_基础.md
+++ b/notes/Docker_基础.md
@@ -19,13 +19,13 @@ Docker 使用 Go 语言进行开发,基于 Linux 内核的 cgroup,namespace
下图体现了 Docker 和传统虚拟化方式的不同之处:传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,再在该系统上运行所需应用进程;而 Docker 容器内的应用进程则是直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟,因此要比传统虚拟机更为轻便。
-
+
## 二、Docker 架构与核心概念
Docker 使用 client-server 架构, Docker 客户端将命令发送给 Docker 守护进程,后者负责构建,运行和分发 Docker 容器。 Docker客户端和守护程序使用 REST API,通过 UNIX 套接字或网络接口进行通信。核心概念如下:
-
+
### 2.1 镜像
@@ -61,7 +61,7 @@ Docker 客户端(docker)是用户与 Docker 交互的主要方式。当你
Docker 提供了大量命令用于管理镜像、容器和服务,命令的统一使用格式为:` docker [OPTIONS] COMMAND` ,其中 OPTIONS 代表可选参数。需要注意的是 Docker 命令的执行一般都需要获取 root 权限,这是因为 Docker 的命令行工具 docker 与 docker daemon 是同一个二进制文件,docker daemon 负责接收并执行来自 docker 的命令,它的运行需要 root 权限。所有常用命令及其使用场景如下:
-
+
### 3.1 基础命令
@@ -277,7 +277,7 @@ docker run -it -p 8080:8080 spring-boot-base-java
这里为了观察到启动效果,所以使用交互的方式启动,实际部署时可以使用`-d`参数来后台启动,输出如下:
-
+
### 5.2 基于 JDK 镜像部署 Spring Boot 项目
diff --git a/notes/Linux_常用Shell命令.md b/notes/Linux_常用Shell命令.md
index 0a7d7a0..b5fec15 100644
--- a/notes/Linux_常用Shell命令.md
+++ b/notes/Linux_常用Shell命令.md
@@ -598,7 +598,7 @@ userdel 命令用于删除用户,格式为:userdel [选项] 用户名。常
使用 `ll` 命令可以查看到文件的详细属性,各个属性的含义如下:
-
+
- \-:普通文件。
- d:目录文件。
- l:链接文件。
@@ -610,7 +610,7 @@ userdel 命令用于删除用户,格式为:userdel [选项] 用户名。常
chmod 用于设置文件或目录的权限,格式为:chmod [参数] 权限 文件或目录名称。 权限支持字符表示和数字表示两种形式,其对应关系如下:
-
+
示例如下:
```
@@ -661,7 +661,7 @@ Vim编辑器有三种状态模式:
- 输入模式:正常的文本录入。
- 末行模式:保存或退出文档,以及设置编辑环境。
-
+
命令模式中常用的命令如下:
| 命令 | 作用 |
diff --git a/notes/MongoDB_分片.md b/notes/MongoDB_分片.md
index eb14fcd..cd9b51a 100644
--- a/notes/MongoDB_分片.md
+++ b/notes/MongoDB_分片.md
@@ -29,7 +29,7 @@
+ **config servers** :配置服务器,它是整个集群的核心,用于存储集群的元数据和配置信息 (如分片服务器上存储了哪些数据块以及这些块的数据范围) 。从 MongoDB 3.4 开始,必须将配置服务器部署为副本集。
+ **mongos** :查询服务的路由器,它是集群对外提供服务的门户。mongos 从配置服务器获取数据块的相关信息,并将客户端请求路由到对应的分片服务器上。
-
+
### 1.2 分片键
@@ -55,11 +55,11 @@
无论采用何种分片策略,数据最终都被存储到对应范围的数据块 (chunk) 上,每个块默认的大小都是 64 M。由于数据源源不断的加入,当块超过指定大小时,就会进行块拆分。需要强调的是块拆分是一个轻量级的操作,因为在本质上并没有任何数据的移动,只是由 config servers 更新关于块的元数据信息。
-
+
当某个分片服务器上的数据过多时候,此时为了避免单服务器上 CPU 和 IO 操作的性能问题,就会发生块迁移,将块从一个分片迁移到另外一个分片,同时 config servers 也会更新相关块的元数据信息。 块迁移是由在后台运行的平衡器 (balancer) 所负责的,它在后台进行持续监控,如果最大和最小分片之间的块数量差异超过迁移阈值,平衡器则开始在群集中迁移块以确保数据的均匀分布。
-
+
块的大小是可以手动进行配置修改,但需要注意权衡利弊:
@@ -70,13 +70,13 @@
这里需要注意块的迁移不会对查询造成任何影响,MongoDB 集群和 Redis 集群的查询原理不同。对于 Redis Cluster 而言,数据的散列规则同时也是查询时的路由规则。但是对于 MongoDB 分片集群而言,查询需要先经过 mongos ,mongos 会从 config servers 上获取块的位置信息和数据范围,然后按照这些信息进行匹配后再路由到正确的分片上。因此,从本质上而言 MongoDB 的分片策略和路由规则没有任何关系,假设按照分片策略将某文档分发到 Shard A 的 Chunk01 上,之后 Chunk01 迁移到 Shard B , 由于配置服务器会更新 Chunk01 块的位置信息,所以仍然能够正确路由到。
-
+
### 1.6 非分片集合
以上的所有讲解都是针对分片集合的,而在实际开发中并非每个集合都需要进行分片,MongoDB 允许在同一数据库下混合使用分片和非分片集合。每个数据库都有自己的主分片,所有非分片集合同一存储在主分片上。需要特别说明的是主分片和复本集中的主节点没有任何关系,在新数据库创建时程序会自动选择当前集群中最少数据量的分片作为主分片。如下图所示: Shard A 为主分片,集合 Collection1 是分片集合,而集合 Collection2 是非分片集合。
-
+
## 二、集群搭建
@@ -284,7 +284,7 @@ db.runCommand({ addshard : "rs1/hadoop001:37018,hadoop002:37018,hadoop003:37018"
务必注意,在添加分片副本集之前一定要切换到管理员角色,否则后面的添加操作会返回 `"ok" : 0` 的失败状态码,同时会提示 `addShard may only be run against the admin database.` 添加成功后,可以使用 `sh.status()` 查看集群状态,此时输出如下,可以看到两个分片副本集已经被成功添加。
-
+
### 2.8 测试分片
@@ -322,7 +322,7 @@ db.users.insertMany(arr);
插入数据完成后,执行 `sh.status()` 命令可以查看分片情况,以及块的数据信息,部分输出如下:
-
+
## 参考资料
diff --git a/notes/MongoDB_复制.md b/notes/MongoDB_复制.md
index 4f692f6..5a57fa6 100644
--- a/notes/MongoDB_复制.md
+++ b/notes/MongoDB_复制.md
@@ -24,7 +24,7 @@
为保证数据安全,实现高可用,MongoDB 提供了复制功能,可以将主节点上的数据复制到多个从节点上,这样即便主节点异常,由于数据是以多副本的方式存储,仍然可以保证数据安全。一个标准的三节点的副本集的架构如下:
-
+
#### 1. 初始同步
@@ -44,7 +44,7 @@ MongoDB 按 namespace 或 document id 对每批操作进行分组,并使用不
虽然仲裁者可以占用更少的服务器资源,但是由于其并不存储数据,所以对数据的安全性并不能起到帮助作用。因此应该尽量避免使用仲裁者,同时尽量保证最多只使用一个仲裁者,即如果节点数量恰好是偶数,则添加一个仲裁者,如果节点数量是奇数,那就不需要仲裁者。
-
+
## 二、故障发现与恢复
@@ -58,7 +58,7 @@ MongoDB 的选举算法会尝试让高优先级的节点优先发起选举,从
-
+
### 2.3 投票成员
@@ -78,7 +78,7 @@ MongoDB 的选举算法会尝试让高优先级的节点优先发起选举,从
如下示例是一个 9 个成员的副本集,包含 7 个投票成员和 2 个无投票成员:
-
+
## 三、搭建副本集
@@ -88,7 +88,7 @@ MongoDB 的选举算法会尝试让高优先级的节点优先发起选举,从
选择所需版本的 MongoDB 进行下载,下载地址为: https://www.mongodb.com/download-center/community
-
+
这里我下载的版本为 `4.0.10` , 安装环境为 `RHEL 7.0`,下载后进行解压:
@@ -180,7 +180,7 @@ rs.initiate( {
使用 `rs.status()` 命令查看副本集状态,部分输出如下。从输出中可以看到 hadoop001 为 PRIMARY 节点,而 hadoop002 和 hadoop003 均为 SECONDARY 节点,此时代表副本集已经搭建成功。
-
+
## 参考资料
diff --git a/notes/MongoDB_索引.md b/notes/MongoDB_索引.md
index 74a3259..2521196 100644
--- a/notes/MongoDB_索引.md
+++ b/notes/MongoDB_索引.md
@@ -112,7 +112,7 @@ db.user.find({}).sort({name:1})
当前大多数数据库都支持双向遍历索引,这和存储结构有关 (如下图)。在 B-Tree 结构的叶子节点上,存储了索引键的值及其对应文档的位置信息,而每个叶子节点间则类似于双向链表,所以如下图既可以从值为 4 的索引遍历到值为 92 的索引,反之亦然。
-
+
### 2.2 复合索引
diff --git a/notes/MySQL_EXPLAIN.md b/notes/MySQL_EXPLAIN.md
index 8050832..4fbe489 100644
--- a/notes/MySQL_EXPLAIN.md
+++ b/notes/MySQL_EXPLAIN.md
@@ -14,7 +14,7 @@ mysql> EXPLAIN SELECT * FROM employees;
注:本篇文章的测试数据来源于 MySQL 官方提供的 [Employees Sample Database](https://dev.mysql.com/doc/employee/en/),其数据库结构如下:
-
+
以下分别介绍 EXPLAIN 输出结果中各个字段的含义:
diff --git a/notes/MySQL_Mycat中间件.md b/notes/MySQL_Mycat中间件.md
index 717fb1d..47b6628 100644
--- a/notes/MySQL_Mycat中间件.md
+++ b/notes/MySQL_Mycat中间件.md
@@ -236,7 +236,7 @@ Mycat 读写分离的配置非常简单,只需要通过配置 balance,writeH
综合以上全部内容,这里给出一个分库分表的示例,其架构如下:
-
+
如上图所示,这里模拟的是一个电商数据库,并对其执行分库分表操作:
diff --git a/notes/MySQL_PXC集群.md b/notes/MySQL_PXC集群.md
index f0cf452..16a834d 100644
--- a/notes/MySQL_PXC集群.md
+++ b/notes/MySQL_PXC集群.md
@@ -24,7 +24,7 @@ Percona XtraDB Cluster (简称 PXC) 是 Percona 公司开源的实现 MySQL 高
+ 允许的最大事务大小由 wsrep_max_ws_rows 和 wsrep_max_ws_size 参数共同定义,因此超大型事务会被拆分为一系列小型事务,如加载大数据集 LOAD DATA INFILELOAD DATA。
+ 由于在集群级别采用乐观锁进行并发控制,所以事务在 COMMIT 阶段仍然有被中止的可能。如两个事务在不同的集群节点上提交对相同的行的写入,此时只有其中一个可以成功提交,另一个将被中止。
-
+
虽然 PXC 集群存在以上限制,但就目前而言,它仍然是解决数据一致性和高可用性的最好方案,其搭建步骤如下:
diff --git a/notes/MySQL_基础.md b/notes/MySQL_基础.md
index 08faec1..18c3a52 100644
--- a/notes/MySQL_基础.md
+++ b/notes/MySQL_基础.md
@@ -44,7 +44,7 @@ InnoDB 是 MySQL 5.5 之后默认的存储引擎,它是一种兼具高可靠
-
+
### 1.2 MyISAM
@@ -119,19 +119,19 @@ mysql> SELECT * FROM total;
**平衡二叉树数据结构**:
-
+
**红黑树数据结构**:
-
+
**Btree 数据结构**:
-
+
**B+ Tree 数据结构**
-
+
> 以上图片均通过数据结构可视化网站 [Data Structure Visualizations](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html) 自动生成,感兴趣的小伙伴也可自行尝试。
@@ -146,10 +146,10 @@ mysql> SELECT * FROM total;
对于 InnoDB ,因为主键索引是聚集索引,所以其叶子节点存储的就是实际的数据。而非主键索引存储则是主键的值 :
-
+
对于 MyISAM,因为主键索引是非聚集索引,所以其叶子节点存储的只是指向数据位置的指针:
-
+
综上所述,B+ tree 普遍适用于范围查找,优化排序和分组等操作。B+ tree 的查找是基于字典序的,因此其适用的具体查询类型如下:
+ **全值匹配**:以索引为条件进行精确查找。如 `emp_no` 字段为索引,查询条件为 `emp_no = 10008`;
@@ -287,25 +287,25 @@ InnoDB 存储引擎完全支持 ACID 模型:
一个事务的更新操作被另外一个事务的更新操作锁覆盖,从而导致数据不一致:
-
+
**2. 脏读**
在不同的事务下,一个事务读取到其他事务未提交的数据:
-
+
**3. 不可重复读**
在同一个事务的两次读取之间,由于其他事务对数据进行了修改,导致对同一条数据两次读到的结果不一致:
-
+
**4.幻读**
在同一个事务的两次读取之间,由于其他事务对数据进行了修改,导致第二次读取到第一次不存在数据,或第一次原本存在的数据,第二次却读取不到,就好像之前的读取是 “幻觉” 一样:
-
+
### 4.4 隔离级别
diff --git a/notes/MySQL_复制.md b/notes/MySQL_复制.md
index 5ebf9bc..1756b60 100644
--- a/notes/MySQL_复制.md
+++ b/notes/MySQL_复制.md
@@ -60,7 +60,7 @@ MySQL 的复制原理如下图所示:
如果没有进行任何配置,主库在将变更写入到二进制日志后,就会返回对客户端的响应,因此默认情况下的复制是完全异步进行的,主备之间可能会短暂存在数据不一致的情况。
-
+
### 2.2 配置步骤
@@ -356,7 +356,7 @@ MMM (Master-Master replication manager for MySQL) 是由 Perl 语言开发的一
除了管理双主节点,MMM 也负责管理所有 Slave节点,在出现宕机、复制延迟或复制错误,MMM 会移除该节点的 VIP,直至节点恢复正常。MMM 高可用的架构示例图如下:
-
+
MMM 架构的缺点在于虽然其能实现自动切换,但却不会主动补齐丢失的数据,所以会存在数据不一致性的风险。另外 MMM 的发布时间比较早,所以其也不支持 MySQL 最新的基于 GTID 的复制,如果你使用的是基于 GTID 的复制,则只能采用 MHA。
@@ -364,7 +364,7 @@ MMM 架构的缺点在于虽然其能实现自动切换,但却不会主动补
MHA (Master High Availability) 是由 Perl 实现的一款高可用程序,相对于 MMM ,它能尽量避免数据不一致的问题。它监控的是一主多从的复制架构,架构如下图所示:
-
+
在 Master 节点宕机后,其处理流程如下:
diff --git a/notes/Nginx_基础.md b/notes/Nginx_基础.md
index b027c86..14d9f1f 100644
--- a/notes/Nginx_基础.md
+++ b/notes/Nginx_基础.md
@@ -39,7 +39,7 @@ Nginx 能够同时支持正向代理和反向代理,这两种代理模式的
+ 正向代理发生在客户端,是客户端主动发起的代理。如我们不能直接访问某个服务器,但可以间接通过中间的代理服务器去进行访问,然后将访问结果再返回给我们。
+ 反向代理发生在服务端,客户端并不知道发生了代理,示例如下。用户只知道将请求发送给 Nginx,但是并不知道请求被转发了,也不知道被转发给了哪一台应用服务器。实际上对于用户来说,他也没必要知道,因为请求结果都是相同的。
-
+
## 二、基本命令
diff --git a/notes/RabbitMQ_基础.md b/notes/RabbitMQ_基础.md
index 5ae5bb2..dc0347a 100644
--- a/notes/RabbitMQ_基础.md
+++ b/notes/RabbitMQ_基础.md
@@ -49,7 +49,7 @@ RabbitMQ 完全实现了 AMQP 协议,并基于相同的模型架构。RabbitMQ
RabbitMQ 与 AMQP 遵循相同的模型架构,其架构示例图如下:
-
+
### 1. Publisher(发布者)
@@ -109,17 +109,17 @@ RabbitMQ 支持多种交换器类型,常用的有以下四种:
这是最简单的一种交换器模型,此时会把消息路由到与该交换器绑定的所有队列中。如下图,任何发送到 X 交换器上的消息,都会被路由到 Q1 和 Q2 两个队列上。
-
+
### 5.2 direct
把消息路由到 BindingKey 和 RountingKey 完全一样的队列中。如下图,当消息的 RountingKey 为 orange 时,消息会被路由到 Q1 队列;当消息的 RountingKey 为 black 或 green 时,消息会被路由到 Q2 队列。
-
+
需要特别说明的是一个交换器绑定多个队列时,它们的 BindingKey 是可以相同的,如下图。此时当消息的 RountingKey 为 black 时,消息会同时被路由到 Q1 和 Q2 队列。
-
+
### 5.3 topic
@@ -130,7 +130,7 @@ RabbitMQ 支持多种交换器类型,常用的有以下四种:
以下是官方文档中的示例,交换器与队列的绑定情况如图所示,此时的路由情况如下:
-
+
+ 路由键为 `lazy.orange.elephant` 的消息会发送给所有队列;
+ 路由键为 `quick.orange.fox` 的消息只会发送给 Q1 队列;
diff --git a/notes/RabbitMQ_高可用集群架构.md b/notes/RabbitMQ_高可用集群架构.md
index b1e83b2..8ceab0d 100644
--- a/notes/RabbitMQ_高可用集群架构.md
+++ b/notes/RabbitMQ_高可用集群架构.md
@@ -35,7 +35,7 @@
-
+
这里对上面的集群架构做一下解释说明:
首先一个基本的 RabbitMQ 集群不是高可用的,虽然集群共享队列,但在默认情况下,消息只会被路由到某一个节点的符合条件的队列上,并不会同步到其他节点的相同队列上。假设消息路由到 node1 的 my-queue 队列上,但是 node1 突然宕机了,那么消息就会丢失,想要解决这个问题,需要开启队列镜像,将集群中的队列彼此之间进行镜像,此时消息就会被拷贝到处于同一个镜像分组中的所有队列上。
@@ -142,7 +142,7 @@ rabbitmqctl set_cluster_name my_rabbitmq_cluster
除了可以使用命令行外,还可以使用打开任意节点的 UI 界面进行查看,情况如下:
-
+
### 2.5 配置镜像队列
@@ -174,7 +174,7 @@ rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'
配置完成后,可以通过 Web UI 界面查看任意队列的镜像状态,情况如下:
-
+
### 2.6 节点下线
@@ -324,7 +324,7 @@ haproxy -f /etc/haproxy/haproxy.cfg
启动后可以在监控页面进行查看,端口为设置的 8100,完整地址为:http://hadoop001:8100/stats,页面情况如下:
-
+
所有节点都为绿色,代表节点健康。此时证明 HAProxy 搭建成功,并已经对 RabbitMQ 集群进行监控。
@@ -505,10 +505,10 @@ systemctl start keepalived
启动后此时 hadoop001 为主节点,可以在 hadoop001 上使用 `ip a` 命令查看到虚拟 IP 的情况:
-
+
此时只有 hadoop001 上是存在虚拟 IP 的,而 hadoop002 上是没有的。
-
+
### 4.6 验证故障转移
@@ -520,7 +520,7 @@ systemctl stop keepalived
此时再次使用 `ip a` 分别查看,可以发现 hadoop001 上的 VIP 已经漂移到 hadoop002 上,情况如下:
-
+
此时对外服务的 VIP 依然可用,代表已经成功地进行了故障转移。至此集群已经搭建成功,任何需要发送或者接受消息的客户端服务只需要连接到该 VIP 即可,示例如下:
diff --git a/notes/Redis_哨兵模式.md b/notes/Redis_哨兵模式.md
index 5931695..ca3aa8b 100644
--- a/notes/Redis_哨兵模式.md
+++ b/notes/Redis_哨兵模式.md
@@ -26,7 +26,7 @@
- 在从节点启动时候加入 `--slaveof {masterHost} {masterPort}` 参数;
- 直接在从节点上执行 `slaveof {masterHost} {masterPort}` 命令。
-
+
主从节点复制关系建立后,可以使用 `info replication` 命令查看相关的复制状态。需要注意的是每个从节点只能有一个主节点,但主节点可以同时拥有多个从节点,复制行为是单向的,只能由主节点复制到从节点,因此从节点默认都是只读模式,即 `slave-read-only` 的值默认为`yes`。
### 1.2 断开复制关系
@@ -51,7 +51,7 @@ slaveof{newMasterIp} {newMasterPort}
哨兵模式的主要作用在于它能够自动完成故障发现和故障转移,并通知客户端,从而实现高可用。哨兵模式通常由一组 Sentinel 节点和一组(或多组)主从复制节点组成,架构如下:
-
+
### 2.1 架构说明
@@ -180,15 +180,15 @@ redis-sentinel sentinel-26381.conf
使用 `ps -ef | grep redis` 命令查看进程,此时输出应该如下:
-
+
可以使用 `info replication` 命令查看 Redis 复制集的状态,此时输出如下。可以看到 6379 节点为 master 节点,并且有两个从节点,分别为 slave0 和 slave1,对应的端口为 6380 和 6381。
-
+
可以使用 `info Sentinel` 命令查看任意 Sentinel 节点的状态,从最后一句输出可以看到 Sentinel 节点已经感知到 6379 的 master 节点,并且也知道它有两个 slaves 节点;同时 Sentinel 节点彼此之间也感知到,共有 3 个 Sentinel 节点。
-
+
## 参考资料
diff --git a/notes/Redis_集群模式.md b/notes/Redis_集群模式.md
index bb1b364..c10a587 100644
--- a/notes/Redis_集群模式.md
+++ b/notes/Redis_集群模式.md
@@ -31,13 +31,13 @@ HASH_SLOT = CRC16(key) mod 16384
假设现在有一个 6 个节点的集群,分别有 3 个 Master 点和 3 个 Slave 节点,槽会尽量均匀的分布在所有 Master 节点上。数据经过散列后存储在指定的 Master 节点上,之后 Slave 节点会进行对应的复制操作。这里再次说明一下槽只是一个虚拟的概念,并不是数据存放的实际载体。
-
+
### 1.2 节点通讯
在 Redis 分布式架构中,每个节点都存储有整个集群所有节点的元数据信息,这是通过 P2P 的 Gossip 协议来实现的。集群中的每个节点都会单独开辟一个 TCP 通道,用于节点之间彼此通信,通信端口号在基础端口上加 10000;每个节点定期通过特定的规则选择部分节点发送 ping 消息,接收到 ping 信息的节点用 pong 消息作为响应,通过一段时间的彼此通信,最终所有节点都会达到一致的状态,每个节点都会知道整个集群全部节点的状态信息,从而到达集群状态同步的目的。
-
+
### 1.3 请求路由
@@ -125,7 +125,7 @@ cluster-config-file nodes-6480.conf
启动所有 Redis 节点,启动后使用 `ps -ef | grep redis` 查看进程,输出应如下:
-
+
接着需要使用以下命令创建集群,集群节点之间会开始进行通讯,并完成槽的分配:
@@ -136,7 +136,7 @@ redis-cli --cluster create 127.0.0.1:6479 127.0.0.1:6480 127.0.0.1:6481 \
执行后输出如下:M 开头的表示持有槽的主节点,S 开头的表示从节点,每个节点都有一个唯一的 ID。最后一句输出表示所有的槽都已经分配到主节点上,此时代表集群搭建成功。
-
+
### 2.3 集群完整性校验
@@ -172,7 +172,7 @@ redis-cli --cluster add-node 127.0.0.1:6485 127.0.0.1:6479
redis-cli -h 127.0.0.1 -p 6479 cluster nodes
```
-
+
想要让新加入的节点能够进行读写操作,可以使用 `reshard` 命令为其分配槽,这里我们将其他三个主节点上的槽迁移一部分到 6485 节点上,这里一共迁移 4096 个槽,即 16384 除以 4 。 `cluster-from ` 用于指明槽的源节点,可以为多个,`cluster-to` 为槽的目标节点,`cluster-slots` 为需要迁移的槽的总数。
@@ -185,7 +185,7 @@ redis-cli --cluster reshard 127.0.0.1:6479 \
迁移后,再次使用 `cluster nodes` 命令可以查看到此时 6485 上已经有其他三个主节点上迁移过来的槽。
-
+
为保证高可用,可以为新加入的主节点添加从节点,命令如下。`add-node` 接收两个参数,第一个为需要添加的从节点,第二个参数为集群内任意节点,用于发现集群。`cluster-master-id` 参数用于指明作为哪个主节点的从节点,如果不加这个参数,则自动分配给从节点较少的主节点。
diff --git a/notes/installation/ElasticSearch+Kibana单机环境搭建.md b/notes/installation/ElasticSearch+Kibana单机环境搭建.md
index 0696e7e..3b6eafe 100644
--- a/notes/installation/ElasticSearch+Kibana单机环境搭建.md
+++ b/notes/installation/ElasticSearch+Kibana单机环境搭建.md
@@ -97,7 +97,7 @@ chown -R heibaiying:heibaiying /usr/app/elasticsearch-7.2.0/
想要验证是否启动成功,可以使用`jps`命令查看`Elasticsearch`进程是否启动,也可以访问`9200`端口,出现如下页面则代表启动成功:
-
+
## 二、Kibana 安装
@@ -137,7 +137,7 @@ nohup ./kibana &
kibana Web UI 的访问端口号为`5601`,出现以下页面则代表启动成功:
-
+
### 2.4 界面汉化
diff --git a/notes/installation/MongoDB单机环境搭建.md b/notes/installation/MongoDB单机环境搭建.md
index 1f4ac8a..6f8f4f3 100644
--- a/notes/installation/MongoDB单机环境搭建.md
+++ b/notes/installation/MongoDB单机环境搭建.md
@@ -11,7 +11,7 @@
下载地址为: https://www.mongodb.com/download-center/community ,选择所需版本的 MongoDB 后进行下载:
-
+
这里我下载的版本为 `4.0.10` , 安装环境为 `RHEL 7.0`,下载后进行解压:
diff --git a/notes/installation/MySQL单机环境搭建.md b/notes/installation/MySQL单机环境搭建.md
index cf8cf70..0a70bba 100644
--- a/notes/installation/MySQL单机环境搭建.md
+++ b/notes/installation/MySQL单机环境搭建.md
@@ -4,7 +4,7 @@
这里我采用的是二进制安装包的方式进行安装,安装包的下载地址为:https://dev.mysql.com/downloads/mysql/ ,按需选择对应的版本后进行下载:
-
+
下载后进行解压,并对解压后的文件夹进行重命名,以便在后面的配置中进行引用:
diff --git a/notes/installation/RabbitMQ单机环境搭建.md b/notes/installation/RabbitMQ单机环境搭建.md
index 8f788cb..9fa3ba4 100644
--- a/notes/installation/RabbitMQ单机环境搭建.md
+++ b/notes/installation/RabbitMQ单机环境搭建.md
@@ -155,7 +155,7 @@ rabbitmq-plugins enable rabbitmq_management
访问端口为 `15672`。默认的用户名和密码都是 `guest` 。如果你所用浏览器和 RabbitMQ 服务不在同一台主机上,此时应该无法登录,并出现下面的提示 :
-
+
之所以会出现这个提示,是因为出于安全考虑,RabbitMQ 只允许在本机使用默认的`guest`用户名登录。想要在其他主机上登录,可以使用自定义的账户。
### 4.2 新增账户
@@ -182,4 +182,4 @@ rabbitmqctl set_user_tags root administrator
登录后可以查看到RabbitMQ 和 Erlang 的版本号,以及对应的账户信息:
-
\ No newline at end of file
+
\ No newline at end of file