
+
## 本地方法栈
@@ -157,11 +157,11 @@ JDK 7 版本及 JDK 7 版本之前,Hotspot 虚拟机的堆结构如下:
- 老年代 (Old Generation)
- 永久代 (Permanent Generation)
-
+
JDK 8 版本之后 HotSpot 虚拟机的永久代被彻底移除了,取而代之是元空间,元空间使用的是直接内存。
-
+
## 堆和栈的关系
@@ -333,7 +333,7 @@ str1、str2 是从字符串常量池中获取的对象。
对于 str5,字符串常量池已有 "helloworld" 对象,str5 直接引用该对象。
-
+
所以,尽量避免多个字符串拼接,因为这样会重新创建新的对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
@@ -353,7 +353,7 @@ str1、str2 是从字符串常量池中获取的对象。
- 实例数据
- 对齐填充
-
+
### 对象头
@@ -444,13 +444,13 @@ Java 对象的创建过程分为以下5步:
如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是**对象的句柄地址**,而句柄中包含了对象实例数据与类型数据各自的具体地址信息 。
-
+
### 直接指针
如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是**对象的地址**。
-
+
这两种对象访问方式各有优势:
diff --git "a/docs/JVM/2_\345\236\203\345\234\276\346\224\266\351\233\206.md" "b/docs/JVM/2_\345\236\203\345\234\276\346\224\266\351\233\206.md"
index da881ac3..020c1779 100644
--- "a/docs/JVM/2_\345\236\203\345\234\276\346\224\266\351\233\206.md"
+++ "b/docs/JVM/2_\345\236\203\345\234\276\346\224\266\351\233\206.md"
@@ -40,7 +40,7 @@ public class Test {
以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。
-
+
**Java 虚拟机使用可达性分析算法来判断对象是否可被回收**,GC Roots 一般包含以下几种:
@@ -165,7 +165,7 @@ obj = null;
### “标记-清除” 算法
-
+
在标记阶段,从根集合进行扫描,会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。
@@ -180,7 +180,7 @@ obj = null;
### ”标记-整理“ 算法
-
+
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
@@ -190,7 +190,7 @@ obj = null;
### ”复制“ 算法
-
+
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
@@ -275,7 +275,7 @@ SafePoint 的选择很重要,如果太少可能导致 GC 等待的时间太长
**现在标准:在最大吞吐量优先的情况下,降低停顿时间**。
-
+
@@ -285,7 +285,7 @@ SafePoint 的选择很重要,如果太少可能导致 GC 等待的时间太长
虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是**根据具体应用场景选择适合自己的垃圾收集器**。
-
+
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 JDK 9 取消了对 Serial+CMS、
ParNew+Serial Old 这两个组合的支持。
@@ -295,7 +295,7 @@ ParNew+Serial Old 这两个组合的支持。
#### 1. Serial 收集器
-
+
Serial 翻译为串行,也就是说它以串行的方式执行。
@@ -307,7 +307,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
#### 2. ParNew 收集器
-
+
它是 Serial 收集器的多线程版本。
@@ -327,7 +327,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
#### 4. Serial Old 收集器
-
+
是 Serial 收集器的老年代版本,也是给 Client 场景下的虚拟机使用。如果用在 Server 场景下,它有两大用途:
@@ -336,7 +336,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
#### 5. Parallel Old 收集器
-
+
是 Parallel Scavenge 收集器的老年代版本。
@@ -344,7 +344,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
#### 6. CMS 收集器
-
+
CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法。
@@ -371,7 +371,7 @@ G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,
堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。
-
+
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
@@ -379,7 +379,7 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。
-
+
如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:
diff --git "a/docs/JVM/5_\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" "b/docs/JVM/5_\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md"
index a9fc6ad3..33dbf928 100644
--- "a/docs/JVM/5_\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md"
+++ "b/docs/JVM/5_\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md"
@@ -36,7 +36,7 @@ ClassFile {
通过分析 ClassFilee,得到 class 文件的组成:
-

+
diff --git "a/docs/JVM/6_\347\261\273\345\212\240\350\275\275\346\234\272\345\210\266.md" "b/docs/JVM/6_\347\261\273\345\212\240\350\275\275\346\234\272\345\210\266.md"
index 9b348480..210636ca 100644
--- "a/docs/JVM/6_\347\261\273\345\212\240\350\275\275\346\234\272\345\210\266.md"
+++ "b/docs/JVM/6_\347\261\273\345\212\240\350\275\275\346\234\272\345\210\266.md"
@@ -1,46 +1,3 @@
-# Java 文件执行过程
-
-Java 文件经过编译后变成 .class 字节码文件,字节码文件通过**类加载器**被搬运到 JVM 中。
-
-
-

-
-
-举例说明 Java 文件执行过程:
-
-```java
-public class Person {
- private String name;
-
- public Person(String name){
- this.name=name;
- }
-
- public void sayHello(){
- System.out.println("Hello! My Name is: " + name);
- }
-}
-```
-
-```java
-public class JVMTest {
- public static void main(String[] args) {
- Person p=new Person("Li Ming");
- p.sayHello();
- }
-}
-```
-
-执行过程如下:
-
-- 首先编译 JVMTest.java 文件得到 JVMTest.class 文件,系统启动一个 JVM 进程,从 classpath 路径中找到 JVMTest.class 文件,将 JVMTest 的类信息加载到方法区中,这个过程称为 JVMTest 类的加载
-- 然后 JVM 执行 JVMTest 的 main 方法
-- main 方法中 `Person p=new Person("Li Ming")` JVM 需要创建一个 Person 对象,但是此时方法区中是没有 Person 类信息的,所以 JVM 需要加载 Person 类,将 Person 类的信息加载到方法区中
-- 加载完 Person 类,JVM 会在对堆中为该 Person 示例分配内存,然后调用构造函数初始化 Person 示例,并且该实例**持有指向方法区中的 Person 类的类型信息的引用**
-- 接着执行 p.sayHello(),JVM 根据 p 的引用找到 Person 对象,然后根据该对象持有的引用定位到方法区中 Person 类类信息的**方法表**,获得 sayHello 方法的字节码地址。
-
-
-
# 类的生命周期
从类被加载到虚拟机内存中开始,到释放内存总共有 7 个阶段:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using),卸载(Unloading)。
@@ -325,7 +282,7 @@ JDK 自带的 BootstrapClassLoader, ExtClassLoader 和 AppClassLoader 负责加
每一个类都有一个对应它的类加载器。系统中的 ClassLoader 在协同工作的时候会默认使用双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。
-
+
### 1. 工作过程
diff --git "a/docs/JVM/7_Java\347\250\213\345\272\217\347\274\226\350\257\221\345\222\214\350\277\220\350\241\214\350\277\207\347\250\213.md" "b/docs/JVM/7_Java\347\250\213\345\272\217\347\274\226\350\257\221\345\222\214\350\277\220\350\241\214\350\277\207\347\250\213.md"
new file mode 100644
index 00000000..6e207c69
--- /dev/null
+++ "b/docs/JVM/7_Java\347\250\213\345\272\217\347\274\226\350\257\221\345\222\214\350\277\220\350\241\214\350\277\207\347\250\213.md"
@@ -0,0 +1,84 @@
+# Java 程序编译和运行过程
+
+Java程序从 \.java 文件创建到程序运行要经过两大过程:
+
+- \.java 文件由编译器编译成 \.class文件
+- 字节码由 JVM 解释运行
+
+## 编译过程
+
+.java 源文件会被 Java编译器进行编译为.class文件:
+
+- Java 编译一个类时,如果这个类所依赖的类还没有被编译,编译器会自动的先编译这个所依赖的类,然后引用。如果 Java 编译器在指定的目录下找不到该类所依赖的类的 \.class文件或者 \.java源文件,则会报
+ "Cant found sysbol" 的异常错误。
+- 编译后的 \.class 文件主要分为两部分:常量池和方法表集合。
+ 常量池记录的是代码出现过的(常量、类名、成员变量等)以及符号引用(类引用、方法引用,成员变量引用等);
+ 方法表集合则记录各个方法的字节码。
+
+
+
+## 运行过程
+
+JVM 并不是在运行时就会把所有使用到的类都加载到内存中,而是用到的时候,才加载进方法区,并且只加载一次。
+Java类运行的过程大概分为两个步骤:
+
+- 类加载
+- 执行类
+
+举例说明 Java 程序运行过程:
+
+```java
+public class Person {
+ private String name;
+
+ public Person(String name){
+ this.name=name;
+ }
+
+ public void sayHello(){
+ System.out.println("Hello! My Name is: " + name);
+ }
+}
+```
+
+```java
+public class JVMTest {
+ public static void main(String[] args) {
+ Person p=new Person("Li Ming");
+ p.sayHello();
+ }
+}
+```
+
+### 1. 类加载
+
+首先编译 JVMTest.java 文件得到 JVMTest.class 文件,系统启动一个 JVM 进程,从 classpath 路径中找到 JVMTest.class 文件,将 JVMTest 的类信息加载到方法区中,这个过程称为 JVMTest 类的加载。
+
+(只有类信息在方法区中,才能创建对象,使用类中的成员变量)
+
+### 2. JVM 找 main() 方法入口
+
+在 main() 方法 入口持有一个指向当前类 (JVMTest) 常量池的指针,常量池中的第一项是一个对 Person 对象的符号引用。
+
+main 方法中 `Person p=new Person("Li Ming"),JVM 需要创建一个 Person 对象,但是此时方法区中是没有 Person 类信息的,所以 JVM 需要加载 Person 类,将 Person 类的信息加载到方法区中。
+
+JVM 以一个直接指向方法区 Person 类的指针替换了常量池中第一项的符号引用。
+
+### 3. 实例化对象
+
+加载完 Person 类的信息以后,JVM 就会在堆中为一个 Person 实例分配内存,然后调用构造方法初始化 Person 实例,并且该实例**持有指向方法区中的 Person 类的类型信息(其中包括方法表)的引用**。
+
+(p 为指向该 Person 实例的引用,会被放到栈中)
+
+### 4. 运行方法
+
+执行 p.sayHello(),JVM 根据栈中 p 的引用找到 Person 对象,然后根据 Person 对象持有的引用定位到方法区中 Person 类类信息的**方法表**,获得 sayHello 方法的字节码地址,然后开始运行方法。
+
+
+

+
+
+
+# 补充
+
+- [main() 方法详解](https://www.cnblogs.com/bingyimeiling/p/10409728.html)
\ No newline at end of file
diff --git "a/docs/JavaBasics/6_\345\217\215\345\260\204.md" "b/docs/JavaBasics/6_\345\217\215\345\260\204.md"
index 19ee9863..ba3e78a5 100644
--- "a/docs/JavaBasics/6_\345\217\215\345\260\204.md"
+++ "b/docs/JavaBasics/6_\345\217\215\345\260\204.md"
@@ -6,7 +6,7 @@ Java 异常是一个描述在代码段中**发生异常的对象**,当发生
## 异常继承体系
-
+
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**。
@@ -18,7 +18,7 @@ Java 异常分为两种:
- 受检异常:**除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于这种异常**。
- 非受检异常:包括 RuntimeException 及其子类和 Error。
-
+
注意:非受检查异常为编译器不要求强制处理的异常,受检异常则是编译器要求必须处置的异常。
@@ -109,7 +109,7 @@ Java规定:一个方法必须捕捉,或者声明抛出方法之外。
## try-catch-finally语句块的执行
-
+
(1) try 块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
diff --git "a/docs/JavaContainer/1_\345\256\271\345\231\250\346\246\202\350\247\210.md" "b/docs/JavaContainer/1_\345\256\271\345\231\250\346\246\202\350\247\210.md"
index 9ea4babc..26515fb0 100644
--- "a/docs/JavaContainer/1_\345\256\271\345\231\250\346\246\202\350\247\210.md"
+++ "b/docs/JavaContainer/1_\345\256\271\345\231\250\346\246\202\350\247\210.md"
@@ -6,7 +6,7 @@
Collection 集合体系图:
-
+
### 1. Set
@@ -37,7 +37,7 @@ Collection 集合体系图:
Map 集合体系图:
-
+
- TreeMap:基于红黑树实现。
diff --git "a/docs/JavaContainer/2_\345\256\271\345\231\250\344\270\255\347\232\204\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/docs/JavaContainer/2_\345\256\271\345\231\250\344\270\255\347\232\204\350\256\276\350\256\241\346\250\241\345\274\217.md"
index dd0797c5..f795cada 100644
--- "a/docs/JavaContainer/2_\345\256\271\345\231\250\344\270\255\347\232\204\350\256\276\350\256\241\346\250\241\345\274\217.md"
+++ "b/docs/JavaContainer/2_\345\256\271\345\231\250\344\270\255\347\232\204\350\256\276\350\256\241\346\250\241\345\274\217.md"
@@ -2,7 +2,7 @@
## 迭代器模式
-
+
Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。
diff --git "a/docs/JavaContainer/3_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - List.md" "b/docs/JavaContainer/3_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - List.md"
index 807c4aa1..161d764e 100644
--- "a/docs/JavaContainer/3_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - List.md"
+++ "b/docs/JavaContainer/3_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - List.md"
@@ -248,7 +248,7 @@ transient Node
first;
transient Node last;
```
-
+
### 2. 添加元素
将元素添加到链表尾部:
diff --git "a/docs/JavaContainer/4_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - Map.md" "b/docs/JavaContainer/4_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - Map.md"
index 33ae11e2..595262cc 100644
--- "a/docs/JavaContainer/4_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - Map.md"
+++ "b/docs/JavaContainer/4_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - Map.md"
@@ -18,7 +18,7 @@ Entry 存储着键值对。它包含了四个字段,从 next 字段我们可
即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用**拉链法**来解决冲突,
同一个链表中存放哈希值相同的 Entry。
-
+
```java
static class Entry implements Map.Entry {
@@ -98,7 +98,7 @@ map.put("K3", "V3");
- 计算键值对所在的桶;
- 在链表上顺序查找,时间复杂度显然和链表的长度成正比。
-
+
### 3. put 操作
diff --git "a/docs/JavaContainer/5_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - \345\271\266\345\217\221\345\256\271\345\231\250.md" "b/docs/JavaContainer/5_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - \345\271\266\345\217\221\345\256\271\345\231\250.md"
index 95408bf7..98396c27 100644
--- "a/docs/JavaContainer/5_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - \345\271\266\345\217\221\345\256\271\345\231\250.md"
+++ "b/docs/JavaContainer/5_\345\256\271\345\231\250\346\272\220\347\240\201\345\210\206\346\236\220 - \345\271\266\345\217\221\345\256\271\345\231\250.md"
@@ -103,7 +103,7 @@ final Segment[] segments;
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
```
-
+
### 2. size 操作
diff --git "a/docs/JavaIO/3_\345\255\227\350\212\202\346\223\215\344\275\234.md" "b/docs/JavaIO/3_\345\255\227\350\212\202\346\223\215\344\275\234.md"
index 9ca0b8b5..c0c9cc63 100644
--- "a/docs/JavaIO/3_\345\255\227\350\212\202\346\223\215\344\275\234.md"
+++ "b/docs/JavaIO/3_\345\255\227\350\212\202\346\223\215\344\275\234.md"
@@ -309,7 +309,7 @@ Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
-
+
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
diff --git "a/docs/JavaIO/6_\347\275\221\347\273\234\346\223\215\344\275\234.md" "b/docs/JavaIO/6_\347\275\221\347\273\234\346\223\215\344\275\234.md"
index e9edb409..eaa5928e 100644
--- "a/docs/JavaIO/6_\347\275\221\347\273\234\346\223\215\344\275\234.md"
+++ "b/docs/JavaIO/6_\347\275\221\347\273\234\346\223\215\344\275\234.md"
@@ -67,7 +67,7 @@ public static void main(String[] args) throws IOException {
- Socket:客户端类
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
-
+
## Datagram
diff --git a/docs/JavaIO/7_NIO.md b/docs/JavaIO/7_NIO.md
index a265619a..537fe3a5 100644
--- a/docs/JavaIO/7_NIO.md
+++ b/docs/JavaIO/7_NIO.md
@@ -65,23 +65,23 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
-
+
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。
-
+
③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
-
+
④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
-
+
⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
-
+
## 文件 NIO 实例
### FileChannel的使用
@@ -427,7 +427,7 @@ NIO 实现了 IO 多路复用中的 **Reactor 模型**,一个线程 Thread 使
应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,
为 FileChannel 配置非阻塞也没有意义。
-
+
使用Selector的优点:
diff --git "a/docs/Java_Concurrency/1_\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/Java_Concurrency/1_\345\237\272\347\241\200\347\237\245\350\257\206.md"
index 4b612ec2..4850e65d 100644
--- "a/docs/Java_Concurrency/1_\345\237\272\347\241\200\347\237\245\350\257\206.md"
+++ "b/docs/Java_Concurrency/1_\345\237\272\347\241\200\347\237\245\350\257\206.md"
@@ -77,7 +77,7 @@ Java 程序是多线程程序,这是因为 JVM 启动至少了主线程和垃
单就一个 CPU 而言两个线程可以解决线程阻塞造成的不流畅问题,其本身运行效率并没有提高, 多 CPU 的并行运算才真正解决了运行效率问题,这也正是并发和并行的区别。
-
+
@@ -513,7 +513,7 @@ yield() 和 wait() 的区别:
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换:
-
+
diff --git "a/docs/Java_Concurrency/2_\345\271\266\345\217\221\347\220\206\350\256\272.md" "b/docs/Java_Concurrency/2_\345\271\266\345\217\221\347\220\206\350\256\272.md"
index dca44b13..e354e7ad 100644
--- "a/docs/Java_Concurrency/2_\345\271\266\345\217\221\347\220\206\350\256\272.md"
+++ "b/docs/Java_Concurrency/2_\345\271\266\345\217\221\347\220\206\350\256\272.md"
@@ -14,7 +14,7 @@ Java 内存模型(即 Java Memory Model,简称 JMM)试图屏蔽各种硬
当 CPU 需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。
-
+
加入 CPU 缓存带来了一些新的问题:
@@ -27,13 +27,13 @@ JMM 规定线程之间的共享变量存放在主内存(主内存就是硬件
**线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成**。
-
+
### 主内存和工作内存的交互
JMM 定义了 8 个操作来完成主内存和工作内存的交互操作。
-
+
- lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
diff --git "a/docs/Java_Concurrency/3_\345\271\266\345\217\221\345\205\263\351\224\256\345\255\227.md" "b/docs/Java_Concurrency/3_\345\271\266\345\217\221\345\205\263\351\224\256\345\255\227.md"
index b2535bd6..b5f51931 100644
--- "a/docs/Java_Concurrency/3_\345\271\266\345\217\221\345\205\263\351\224\256\345\255\227.md"
+++ "b/docs/Java_Concurrency/3_\345\271\266\345\217\221\345\205\263\351\224\256\345\255\227.md"
@@ -139,7 +139,7 @@ public class SynchronizedDemo {
}
```
-
+
任意一个对象都拥有自己的 Monitor,当这个对象由同步块或者同步方法调用时, 执行方法的线程必须先获取该对象的 Monitor 才能进入同步块和同步方法, 如果没有获取到 Monitor 的线程将会被阻塞在同步块和同步方法的入口处,进入到 BLOCKED 状态。
diff --git "a/docs/Java_Concurrency/6_\345\271\266\345\217\221\345\256\271\345\231\250.md" "b/docs/Java_Concurrency/6_\345\271\266\345\217\221\345\256\271\345\231\250.md"
index 8372a11d..910c97a6 100644
--- "a/docs/Java_Concurrency/6_\345\271\266\345\217\221\345\256\271\345\231\250.md"
+++ "b/docs/Java_Concurrency/6_\345\271\266\345\217\221\345\256\271\345\231\250.md"
@@ -113,7 +113,7 @@ final Segment[] segments;
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
```
-
+
### 2. size 操作
diff --git "a/docs/Java_Concurrency/7_\345\271\266\345\217\221\345\267\245\345\205\267.md" "b/docs/Java_Concurrency/7_\345\271\266\345\217\221\345\267\245\345\205\267.md"
index 9ff22a05..35812b70 100644
--- "a/docs/Java_Concurrency/7_\345\271\266\345\217\221\345\267\245\345\205\267.md"
+++ "b/docs/Java_Concurrency/7_\345\271\266\345\217\221\345\267\245\345\205\267.md"
@@ -113,7 +113,7 @@ final Segment[] segments;
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
```
-
+
### 2. size 操作
@@ -258,7 +258,7 @@ java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J.
维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
-
+
```java
public class CountdownLatchExample {
@@ -307,7 +307,7 @@ public CyclicBarrier(int parties) {
}
```
-
+
```java
public class CyclicBarrierExample {
diff --git "a/docs/Java_Concurrency/8_\347\272\277\347\250\213\346\261\240.md" "b/docs/Java_Concurrency/8_\347\272\277\347\250\213\346\261\240.md"
index ecdbd567..3dc260da 100644
--- "a/docs/Java_Concurrency/8_\347\272\277\347\250\213\346\261\240.md"
+++ "b/docs/Java_Concurrency/8_\347\272\277\347\250\213\346\261\240.md"
@@ -6,7 +6,7 @@ Executor 框架是在 Java5 中引入的,**通过该框架来控制线程的
Executor 基于**生产者-消费者模式**,**提交任务的线程相当于生产者,执行任务的线程相当于消费者**。同时,Executor 的实现还提供了对任务执行的生命周期管理的支持。
-
+
- Executor
@@ -48,7 +48,7 @@ Eexcutor 框架由 3 大部分组成:
异步任务需要返回结果,提交任务后需要返回 Future, FutureTask 实现。
-
+
运行过程:
@@ -190,7 +190,7 @@ private static ExecutorService executor =
状态转换图:
-
+
shutdown() 和 shutdownNow() 这两个方法的原理都是**遍历线程池中所有的线程,然后依次中断线程**。 shutdown() 和 shutdownNow() 还是有不一样的地方:
diff --git "a/docs/Java_Concurrency/9_\345\271\266\345\217\221\345\256\236\350\267\265.md" "b/docs/Java_Concurrency/9_\345\271\266\345\217\221\345\256\236\350\267\265.md"
index 24738af6..3707d113 100644
--- "a/docs/Java_Concurrency/9_\345\271\266\345\217\221\345\256\236\350\267\265.md"
+++ "b/docs/Java_Concurrency/9_\345\271\266\345\217\221\345\256\236\350\267\265.md"
@@ -6,7 +6,7 @@
假设线程 A 持有资源 1,线程 B 持有资源 2,它们同时都想申请对方的资源,那么这两个线程就会互相等待而进入死锁状态。
-
+
使用 Java 代码模拟上述死锁场景:
@@ -533,7 +533,7 @@ public class ThreadLocalExample1 {
其对应的底层结构图为:
-
+
由上图可以看出,ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。
diff --git a/docs/Kafka&RabbitMQ/Kafka.md b/docs/Kafka&RabbitMQ/Kafka.md
new file mode 100644
index 00000000..9d38cbb8
--- /dev/null
+++ b/docs/Kafka&RabbitMQ/Kafka.md
@@ -0,0 +1,331 @@
+# 一、Kafka 的相关概念
+
+Kafka 是一种高吞吐、分布式、基于**发布订阅模型**的消息系统。Kafka 用于离线和在线消息的消费。主要有以下 3 个功能:
+
+- 消息队列:发布和订阅消息流
+- 容错的持久化方式存储记录消息流:Kafka 将消息数据按顺序保存在磁盘上,并在集群内以副本的形式存储以防止数据丢失
+- 流式处理平台:在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。
+
+此外,Kafka 依赖 Zookeeper 进行集群的管理。
+
+## Message
+
+消息(Message)是 Kafka 中**最基本的数据单元**。Kafka 消息由一个**定长的 Header 和变长的字节数组**组成。
+
+## Broker
+
+Kafka 集群包含一个或多个服务器,这些服务器就被称为 Broker。
+
+## Topic
+
+Kafka 根据主题(Topic)对消息进行归类,**发布到 Kafka 集群的每条消息(Message)都需要指定一个 Topic**。
+
+## Partition
+
+Partition 即分区,每个 Topic 包含一个或多个分区。
+
+消息发送时都被发送到一个 Topic 中,其本质就是一个目录,而 Topic 由是由一些 Partition Logs(分区日志)组成,其组织结构如下:
+
+
+
+**每个 Partition 中的消息都是有序的**,生产的消息被不断追加到 Partition Log 上,其中的每一个消息都被赋予了一个唯一的 offset 值,Kafka 通过 **offset 保证消息在分区内的顺序**,offset 的顺序性不跨分区,即 Kafka 只保证在同一个分区内的消息是有序的;同一 Topic 的多个分区内的消息,Kafka 并不保证其顺序性。
+
+**Kafka 集群会保存所有的消息,不管消息有没有被消费**;可以设定消息的过期时间,只有过期的数据才会被自动清除以释放磁盘空间。比如设置消息过期时间为 2 天,那么这 2 天内的所有消息都会被保存到集群中,数据只有超过了 2 天才会被清除。
+
+Kafka 需要维持的元数据只有一个,即消费消息在 Partition 中的 offset 值,Consumer 每消费一个消息,offset就会 +1。其实消息的状态完全是由 Consumer 控制的,**Consumer 可以跟踪和重设这个 offset 值,Consumer 就可以读取任意位置的消息**。
+
+把消息日志以 Partition 的形式存放有多重考虑:
+
+- 第一,方便在集群中扩展,每个 Partition 可以通过调整以适应它所在的机器,而一个 Topic 又可以由多个Partition 组成,因此整个集群就可以适应任意大小的数据了;
+- 第二,可以提高并发,因为是**以 Partition 为单位进行读写**,Partition 的个数对应了消费者个生产者的并发度,比如 Partition 的个数为 3,则集群中最多同时有 3 个线程的消费者并发处理数据。
+
+注意:Partition 并不是越多越好的(Partition 的个数不能超过 Broker 结点),原因如下:
+
+- 分区越多,服务端和客户端需要使用的**内存**就越多。
+- 会降低一定的可用性。某个 Leader 挂了,相比较较少分区的情况,重新选出 Leader,花的时间就会更长。
+
+## Replication
+
+Kafka 中每个 Partition 可以有多个副本(Replication),每个副本中包含的消息是一样的。
+
+每个分区的副本集合中,都会选举出一个副本作为 Leader 副本,Kafka 在不同的场景下会采用不同的选举策略。**所有的读写请求都由选举出的 Leader 副本处理**,**Follower 副本仅仅是从 Leader 副本处把数据拉取(pull)到本地之后,同步更新到自己的 Log 中**。
+
+一般情况下,同一分区的多个副本会被分配到不同的 Broker上。当 Leader 所在的 Broker 宕机之后,可以重新选举新的 Leader,继续对外提供服务。
+
+
+
+## Producer
+
+消息生产者(Producer),向 Broker 发送消息的客户端。
+
+**Producer 直接发送消息到 Broker上的 Leader Partition**,不需要经过任何中介或其他路由转发。
+
+**Producer 客户端自己控制着消息被推送(push)到哪些 Partition**。Kafka 提供了接口供用户实现自定义的 Partition,用户可以为每个消息指定一个 Partition Key,通过这个 Key 来实现一些 Hash 分区算法。比如,把 userid 作为 Partition Key 的话,相同 userid 的消息将会被推送到同一个 Partition。**Producer 可以通过随机或者 Hash 等方式将消息平均发送到多个 Partition 上以实现负载均衡**。
+
+### 批量发送消息
+
+批量发送消息是提高吞吐量的重要方式。Kafka Producer 可以将消息在内存中累计到一定数量后作为一个**批量发送请求**。批量发送的数量大小可以通过Producer 的参数控制,参数值可以设置为累计的消息的数量(如 500 条)、累计的时间间隔(如 100ms )或者累计的数据大小(64 KB)。通过增加 Batch 的大小,可以减少网络请求和磁盘 I / O 的次数,当然具体参数设置需要在**效率**和**时效性**方面做一个权衡。
+
+### 压缩消息
+
+Producer 端可通过 gzip 或者 snappy 格式对消息集合进行压缩。消息在 Producer 端进行压缩,在 Consumer 端进行解压。压缩的好处是减少网络传输的数据量,减轻对网络带宽传输的压力。
+
+### 异步并行发送消息
+
+Producer 可以**异步地并行地**向 Kafka发送消息,但是通常 Producer 在发送完消息之后会得到一个 future 响应,返回的是 offset 值或者发送过程中遇到的错误。通过 request.required.acks 参数来设置 Leader Partition 收到确认的副本个数。ack 参数的具体说明如下:
+
+| ack | 说明 |
+| :--: | :----------------------------------------------------------: |
+| 0 | Producer **不会等待 Broker 的响应**
Producer 无法知道消息是否发送成功, 这样**可能会导致数据丢失**,但会得到最大的系统吞吐量。 |
+| 1 | Producer 会在 **Leader Partition** 收到消息时得到 Broker 的一个确认
这样会有更好的可靠性,因为客户端会等待直到 Broker 确认收到消息。 |
+| -1 | Producer 会在**所有备份的 Partition** 收到消息时得到 Broker 的确认
这个设置可以得到最高的可靠性保证。 |
+
+发布消息时,Kafka Client 先构造一条消息,将消息加入到消息集 set 中(Kafka支持批量发布,可以往消息集合中添加多条消息,一次行发布),send 消息时,Producer Client 需指定消息所属的 Topic。
+
+## Consumer
+
+消息消费者(Consumer),从 Broker 读取消息的客户端。
+
+消费者(Consumer)的主要工作是从 Topic 中拉取消息,并对消息进行消费。某个消费者消费到 Partition 的哪个位置(offset)的相关信息,是 Consumer 自己维护的。Consumer 可以自己决定如何读取 Kafka 中的数据。比如,Consumer 可以通过重设 offset 值来重新消费已消费过的数据。不管有没有被消费,Kafka 会保存数据一段时间,这个时间周期是可配置的,只有到了过期时间,Kafka 才会删除这些数据。
+
+这样设计非常巧妙,**避免了 Kafka Server 端维护消费者消费位置的开销**,尤其是在消费数量较多的情况下。另一方面,如果是由 Kafka Server 端管理每个 Consumer 消费状态,一旦 Kafka Server 端出现延时或是消费状态丢失,将会影响大量的 Consumer。另一方面,这一设计也提高了 Consumer 的灵活性,Consumer 可以按照自己需要的顺序和模式拉取消息进行消费。例如:Consumer 可以通过修改其消费的位置实现针对某些特殊 key 的消息进行反复消费,或是跳过某些消息的需求。
+
+Kafka 提供了两套 Consumer Api,分为 Simple Api 和 High-Level Api。
+
+- Simple Api 是一个底层的 API,它维持了一个和单一 Broker 的连接,并且这个 API 是完全无状态的,每次请求都需要指定 offset 值,因此,这套 API 也是最灵活的。
+
+- High-Level API 封装了对集群中一系列 Broker 的访问,可以透明的消费一个 Topic。它自己维持了已消费消息的状态,即每次消费的都是下一个消息。
+
+ High-Level API 还支持以组的形式消费 Topic,如果 Consumers 有同一个组名,那么 Kafka 就相当于一个队列消息服务,而各个 Consumer 均衡地消费相应 Partition 中的数据。若 Consumers 有不同的组名,那么此时 Kafka 就相当于一个广播服务,会把 Topic 中的所有消息广播到每个 Consumer。
+
+ 
+
+## Consumer Group
+
+Consumer Group 即消费者组,多个 Consumer 可以组成一个 Consumer Group,一个 Consumer 只能属于一个 Consumer Group。**同一 Consumer Group 中的多个 Consumer 不能同时消费同一个 Partition 上的数据。**如果不同 Consumer Group 订阅了同一 Topic,Consumer Group 彼此之间不会干扰。这样,如果要实现一个消息可以被多个消费者同时消费(“广播”)的效果,则将每个消费者放入单独的一个 Consumer Group;如果要实现一个消息只被一个消费者消费(“独占”)的效果,则将所有的 Consumer 放入一个 Consumer Group 中。
+
+注意:Consumer Group 中消费者的数量并不是越多越好,当其中消费者数量超过分区的数量时,会导致有消费者分配不到分区,从而造成消费者的浪费。
+
+Producer、Consumer 和 Consumer Group 之间的关系如下图:
+
+
+
+
+
+# 二、Kafka 与 Zookeeper 关系
+
+Zookeeper 为 Kafka 提供集群的管理。不仅保存着集群的 Broker、Topic、Partition 等元数据,还负责 Broker 故障发现、Leader 选举、负载均衡等。
+
+- Broker 元数据管理
+
+ 在 Zookeeper 上会有一个专门**用来进行 Broker 服务器列表记录**的节点。每个 Broker 在启动时,都会到 Zookeeper 上进行注册,即到 `/brokers/ids` 下创建属于自己的节点,每个 Broker 会将自己的 IP 地址和端口等信息记录到该节点中去。
+
+- Topic 元数据管理
+
+ 在 Kafka 中,同一个Topic 的消息会被分成多个分区并将其分布在多个 Broker 上,这些分区信息及与 Broker 的对应关系由 Zookeeper 维护。比如 my-topic 的 Topic 有 2 个分区,对应到 Zookeeper 中会创建这些文件夹:`/brokers/topics/my-topic/Partitions/0`、`/brokers/topics/my-topic/Partitions/1`
+
+- 负载均衡
+
+ 对于同一个 Topic 的不同 Partition,Kafka 会将这些 Partition 分布到不同的 Broker 服务器上,生产者产生消息后也会尽量投递到不同 Broker 的 Partition 中,当 Consumer 消费的时候,Zookeeper 可以根据当前的 Partition 数量以及 Consumer 数量来实现动态负载均衡。
+
+# 三、Kafka 集群
+
+## 典型拓扑结构
+
+
+
+Kafka 集群包含若干个 Producer,若干个 Broker (Kafka 集群支持水平扩展,一般 Broker 数量越多,整个 Kafka 集群的吞吐量也就越高),若干个 Consumer Group,以及一个 Zookeeper 集群。
+
+Kafka 通过 Zookeeper 管理集群配置。
+
+Producer 使用 Push 模式将消息发布到 Broker 上,Consumer 使用 Pull 模式从 Broker 上订阅并消费消息。
+
+## Kafka 数据流
+
+
+
+Producers 往 Brokers 中指定的 Topic Push 消息,Consumers 从 Brokers 里面 Pull 指定 Topic 的消息,然后进行业务处理。
+
+图中有两个 Topic,并且每个 Partition 上色数据在其他 Broker 服务器上都有一份副本:
+
+- Topic-0 有两个 Partition,Partition-0 和 Partition-1;
+- Topic-1 有一个 Partition。
+
+可以看到 Consumer-Group-1 中的 Consumer-2 没有分到 Partition 处理,这是有可能出现的。
+
+# 四、Kafka 的数据存储设计
+
+## Segment 数据文件
+
+Partition 在物理上由多个 Segment 数据文件组成,每个 Segment 数据文件大小相等、按顺序读写。每个 Segment 数据文件的第一个文件名从 0 开始,后续每个 Segment 文件名为上一个全局 Partition 的最大offset,文件扩展名为 .log。在查找指定 Offset 的 Message 中,用二分查找就可以定位到该 Message 在哪个 Segment 数据文件中。
+
+Segment 数据文件会首先被存储在内存中,当 Segment 上的消息条数达到配置值或消息发布时间超过阈值时,Segment 上的消息会被 flush 到磁盘,只有 flush 到磁盘上的消息才能被 Consumer 消费,Segment 达到一定的大小(默认是 1 GB,可配置)后将不会再往该 Segment写数据,Broker 会创建新的 Segment。
+
+## Segment 索引文件
+
+Kafka 为每个 Segment 数据文件都建立了索引文件,索引文件的文件名与数据文件的文件名一致,不同的是索引文件的扩展名为 .index。比如:
+
+
+
+Segment 索引文件索引文件并不会为数据文件中的每条 Message 都建立索引,而是采用系数索引的方式,每个一定字节建立一条索引。这样可有效降低索引文件大小,方便将索引文件加载到内存中,提高集群的吞吐量。
+
+如下图所示,000000000000036869.index 文件中记录了 (3,497) ,在数据文件中表示第 3 个 Message(在全局Partition 表示第 368772 个 Message),该 Message 的物理偏移地址为 497。
+
+
+
+## Partition 中通过 Offset 查找 Message
+
+比如读取 Offset=368776 的 Message,需要通过下面 2 个步骤进行查找:
+
+- 查找 Segment 文件
+
+ 其中00000000000000000000.index 表示最开始的文件,起始 Offset=0。第二个文件 00000000000000368769.index 文件的起始偏移量为 368770 (368769 + 1)。同样,第三个文件00000000000000737337.index 文件的起始偏移量为 737338 (737337 + 1) ,其他后续文件依次类推,以起始偏移量命名并排序这些文件,根据 Offset **二分查找**文件列表,快速定位到 00000000000000368769.index 文件。
+
+- 通过 Segment 文件查找 Message
+
+ 当 Offset=368776 时,依次定位到 00000000000000368769.index 的元数据物理位置和 00000000000000368769.log 的物理偏移地址,然后再通过 00000000000000368769.log 顺序查找直到 Offset=368776为止。
+
+# 五、Kafka 的高可用原理
+
+Kafka 集群由若干个 Broker 组成,Topic 由若干个 Partition 组成,每个 Partition 可存在不同的 Broker 上。可以这样说,一个 Topic 的数据,分散在多个机器上,即每个机器上都存放一部分数据。
+
+## Kafka 0.8 以前
+
+Kafka 0.8 以前是没有高可用机制的。
+
+假设一个 Topic,由 3 个 Partiton 组成。3 个 Partition 在不同机器上,如果其中某一台机器宕掉了,则 Topic 的部分数据就丢失了。
+
+## Kafka 0.8 以后
+
+Kafka 0.8 以后,通过**副本机制**来实现高可用。
+
+每个 Partition 的数据都会同步到其他机器上,形成多个 Partition 副本。从每个 Partition 的副本集合中,选举出 Leader,其他的都是 Follower。Producer 和 Consumer 就和 Leader 打交道:
+
+- 写数据时,Leader 会将所用数据都同步到 Follower 上
+- 读数据时,直接读取 Leader 上的数据
+
+这样,若某个 Broker 宕机了,Broker 上的 Partition 在其他机器上是有副本的;若宕机的 Broker 上面有某个 Partition 的 Leader,则此时会从 Follower 中重新选择一个新的 Leader 出来。
+
+注意:Leader 的读写细节
+
+- 写数据时,Producer 向 Leader 写入,接着其他 Follower 主动从 Leader 中 pull 数据,当所有 Follower 同步好数据,就发送确认信息给 Leader,Leader 收到 Follower 的确认后,就返回写成功消息给 Producer。
+- 读数据时,只会从 Leader 中读取,但当只有一个消息已被所有 Follower 都同步成功,返回确认后时,这个消息才被消费者读到。
+
+# 六、Kafka 中一些常见的问题
+
+## 消息消费的顺序问题
+
+消息在被追加到 Partition 的时候都会分配一个特定的偏移量(offset),Kafka 通过偏移量(offset)来保证消息在分区内的顺序性。为了保证 Kafka 中消息消费的顺序,可以采用以下 2 种方法:
+
+- 设置 1 个 Topic 只对应一个 Partition
+
+ 破坏了 Kafka 的设计初衷,不推荐使用。
+
+- 发送消息的时候指定 key
+
+ 同一个 key 的消息可以保证只发送到同一个 Partition。
+
+**提升:[如何保证消息的顺序性?](https://doocs.github.io/advanced-java/#/./docs/high-concurrency/how-to-ensure-the-order-of-messages)**
+
+## 消息丢失问题
+
+### Producer 丢失数据
+
+如果 Producer 端设置了 `acks=all`,则不会丢失数据。
+
+Leader 在所有的 Follower 都同步到了消息之后,才认为本次写成功。如果没满足这个条件,生产者会进行无限次重试。
+
+### Consumer 丢失数据
+
+默认情况下,Kafka 会自动提交 Offset,Kafka 认为 Consumer 已经处理消息了,但是 Consumer 可能在处理消息的过程中挂掉了。重启系统后,Consumer 会根据提交的 Offset 进行消费,也就丢失了一部分数据。
+
+解决:关闭自动提交 Offset,在处理完之后自己手动提交 Offset,就可以保证数据不会丢失。但可能会存在消息重复消费问题。
+
+### Broker 丢失数据
+
+比较常见的一个场景:Kafka 某个Leader 所在的 Broker 宕机,需要重新选举新的 Leader ,但此时其他的 Follower 部分数据尚未同步完成,选举某个 Follower 成 Leader 后就丢失一部分数据。
+
+所以此时一般设置如下 4 个参数:
+
+- Topic 设置 `replication.factor` 参数
+
+ 参数值必须大于 1,要求每个 Partition 必须有至少 2 个副本。
+
+- Kafka 服务端设置 `min.insync.replicas` 参数
+
+ 参数值必须大于 1,要求每个 Partition 必须有至少 2 个副本。
+
+- Producer 设置 `acks=all`
+
+ 要求每条数据,必须是**写入所有副本,才认为写成功**。
+
+- Producer 端设置 `retries=MAX`
+
+ MAX 即是一个超级大的数字,表示无限次重试。`retries=MAX `要求一旦写入数据失败,就无限重试。
+
+## 消息重复消费问题
+
+Consumer 消费了数据后,每个一段时间,会将已消费过的消息的 Offset 进行提交,这样,重启后,可以继续从上次消费过的 Offset 来继续消费。测试时,直接 kill 进程,然后再重启后,会导致 Consumer 将有些消息处理了,但是还未来得及提交 Offset,重启后,少数消息会再消费一次。
+
+解决:需要结合具体业务来思考,可从以下几个思路来考虑:
+
+- 如果要将数据写入数据库中,先根据主键查查询,如果这数据已存在,就不用插入数据了。
+- 向 Redis 中写入数据,可以使用 set,这样数据不会重复
+- 基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
+
+# 七、Kafka 特性总结
+
+Kafka 特性可总结如下:
+
+## 1. 高可用
+
+Kafka 0.8 以前是没有高可用机制的。
+
+Kafka 0.8 以后,通过**副本机制**来实现高可用,基于**副本机制**实现 Kafka 的高可用。
+
+## 2. 持久性
+
+Kafka 集群接收到 Producer 发过来的消息后,将其持久化到磁盘。此外,还支持数据备份。
+
+## 3. 数据不易丢失
+
+通过合理的配置,Kafka 消息不易丢失。
+
+## 4. 高吞吐量
+
+Kafka 高吞吐量的原因:分区、批量发送和压缩消息、顺序读写、零拷贝
+
+### 分区
+
+当生产者向对应 Topic 传递消息,消息通过**负载均衡机制**传递到不同的 Partition 以减轻单个服务器实例的压力;
+
+一个 Consumer Group 中可以有多个 Consumer,多个 Consumer 可以同时消费不同 Partition 的消息,大大的提高了消费者的并行消费能力。
+
+### 批量发送和压缩消息
+
+- 批量发送:在发送消息的时候,Kafka 不会直接将少量数据发送出去,否则每次发送少量的数据会增加网络传输频率,降低网络传输效率。Kafka 会先将消息缓存在内存中,当超过一个的大小或者超过一定的时间,那么会将这些消息进行批量发送。
+- 端到端压缩消息: Kfaka会将这些批量的数据进行压缩,将一批消息打包后进行压缩,发送给 Broker 服务器后,最终这些数据还是提供给消费者用,所以数据在服务器上还是保持压缩状态,不会进行解压,而且频繁的压缩和解压也会降低性能,最终还是以压缩的方式传递到消费者的手上,在 Consumer 端进行解压。
+
+### 顺序读写
+
+Kafka 是个可持久化的日志服务,它将数据以数据日志的形式进行追加,最后持久化在磁盘中。
+
+Kafka 消息存储时依赖于**文件系统**。为了利用数据的**局部相关性**:操作系统从磁盘中**以数据块为单位**读取数据,将一个数据块读入内存中,如果有相邻的数据,就不用再去磁盘中读取。所以,在某些情况下,**顺序磁盘访问能比随机内存访问还要快**。同时在写数据的时候也是将一整块数据块写入磁盘中,大大提升 I / O 效率。
+
+### 零拷贝
+
+普通的数据拷贝 read & write:
+
+
+
+零拷贝主要的任务是**避免 CPU 做大量的数据拷贝任务,减少不必要的拷贝**。
+
+**内存映射文件(Memory Mapped Files,mmap)**在 64 位操作系统中一般可以表示 20G 的数据文件,它的工作原理是直接利用操作系统的页缓存来实现文件到物理内存的直接映射。
+
+
+
+显然,使用 mmap 替代 read 很明显减少了 1 次拷贝,当拷贝数据量很大时,无疑提升了效率。
+
+# 补充
+
+- [Kafka史上最详细原理总结](https://blog.csdn.net/lingbo229/article/details/80761778?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0.no_search_link&spm=1001.2101.3001.4242)
\ No newline at end of file
diff --git a/docs/Kafka&RabbitMQ/Kafka_RabbitMQ.md b/docs/Kafka&RabbitMQ/Kafka_RabbitMQ.md
new file mode 100644
index 00000000..a9ebb4a9
--- /dev/null
+++ b/docs/Kafka&RabbitMQ/Kafka_RabbitMQ.md
@@ -0,0 +1,47 @@
+## Kafka 和 RabbitMQ 比较
+
+从以下几个方面比较 Kafka 和 RabbitMQ:
+
+- 吞吐量
+
+ Kafka:十万数量级,高吞吐量
+
+ RabbitMQ:万数量级
+
+- Topic 数量对吞吐量影响
+
+ Kafka 的 Topic 可达百/千级,吞吐量下降幅度小,在同等机器下,可以支撑大量的 Topic。
+
+ RabbitMQ 无 Topic 概念。
+
+- 时效性
+
+ Kafka 毫秒级;RabbitMQ 微秒级
+
+- 可用性
+
+ Kafka 基于分布式架构,可用性非常高
+
+ RabbitMQ 基于主从架构实现高可用
+
+- 可靠性
+
+ Kafka 优化参数配置,可以做到零丢失
+
+ RabbitMQ 基本不会丢失数据
+
+- 功能
+
+ Kafka 功能较为简单,主要支持简单的消息队列功能,在大数据领域的实时计算以及日志采集被大规模使用
+
+ RabbitMQ 基于 ErLang 开发,并发能力很强,性能极好,延时很低
+
+总结:
+
+- Kafka 主要特点是基于 Pull 的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8 版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,**适合产生大量数据的互联网服务的数据收集业务**。
+- RabbitMQ 是使用 Erlang 语言开发的开源消息队列系统,基于 AMQP 协议来实现。AMQP 的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP 协议更多用在企业系统内,**对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次**。
+
+
+
+
+
diff --git a/docs/Kafka&RabbitMQ/RabbitMQ.md b/docs/Kafka&RabbitMQ/RabbitMQ.md
new file mode 100644
index 00000000..45ae2732
--- /dev/null
+++ b/docs/Kafka&RabbitMQ/RabbitMQ.md
@@ -0,0 +1,1930 @@
+# 一、Rabbit 概述
+
+RabbitMQ 是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用中间共享数据,RabbitMQ 是使用 **Erlang 语言**来编写的,并且 RabbitMQ 是基于 AMQP 协议的。
+
+特点:
+
+- 开源、性能优秀
+
+ **Erlang 语言**最初用在交换机的架构模式,这样使得 RabbitMQ 在 Broker 之间进行数据交互的性能时非常优秀的。Erlang 的优点:Erlang 有着和原生 Socket 一样的延迟。
+
+- 可靠性
+
+ 提供可靠性消息投递模式(confirm)、返回模式(return)。
+
+- 扩展性
+
+ 多个RabbitMQ 节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
+
+- 与 SpringAOP 完美的整合、API 丰富
+- 保证数据不丢失的前提做到高可靠性、可用性
+
+# 二、AMQP 协议
+
+AMQP (Advanced Message Queuing Protocol) 即高级消息队列协议,是一个进程间传递**异步消息**的**网络协议**。
+
+## AMQP 模型
+
+
+
+工作过程如下:首先发布者(Publisher)发布消息(Message),经由交换机 Exchange。交换机根据**路由规则**将收到的消息分发给与该交换机绑定的 Queue。最后 AMQP 代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。
+
+关于 AMQP 模型的几点说明:
+
+- 发布者、交换机、队列、消费者都可以有多个。AMQP 是一个网络协议,所以这个过程中的发布者,消费者,消息代理可以分别存在于不同的设备上。
+- 布者发布消息时可以给消息指定各种消息属性(Message Meta-data)。有些属性有可能会被消息代理(Brokers)使用,然而其他的属性则是完全不透明的,它们只能被接收消息的应用所使用。
+- 从安全角度考虑,网络是不可靠的,又或是消费者在处理消息的过程中意外挂掉,这样没有处理成功的消息就会丢失。基于此原因,AMQP 模块包含了一个消息确认机制:当一个消息从队列中投递给消费者后,不会立即从队列中删除,直到它收到来自消费者的确认回执(Acknowledgement)后,才完全从队列中删除。
+- 在某些情况下,例如当一个消息无法被成功路由时(无法从交换机分发到队列),消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了**延期操作**,消息会被放入一个**死信队列**中。此时,消息发布者可以选择某些参数来处理这些特殊情况。
+
+## Producer & Consumer
+
+消息生产者(Producer),向 Broker 发送消息的客户端。
+
+消息消费者(Consumer),从 Broker 消费消息的客户端。
+
+## Broker
+
+一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者 RabbitMQ 服务实例。大多数情况下可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。
+
+## Exchange
+
+Exchange 即交换器,是用来发送消息的 AMQP 实体。Exchange 拿到一个消息之后将它**路由**给一个或零个队列。Exchange 使用哪种路由算法是由**交换机类型**和**绑定(Bindings)规则**所决定的。
+
+### Binding
+
+**Producer 将消息发给 Exchange 时,一般会指定一个 RoutingKey (路由键),用来指定这个消息的路由规则,而这个 RoutingKey 需要与交换器类型和 BindingKey (绑定键) 联合使用才能最终生效**。
+
+RabbitMQ 中通过 **Binding (绑定)** 将 Exchange 与 Queue(消息队列) 关联起来,在绑定时一般会指定一个 BindingKey,这样 RabbitMQ 就知道如何正确将消息路由到 Queue 中。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则。
+
+生产者将消息发送给交换器,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。注意BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如 fanout 类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。
+
+### Exchange 类型
+
+Exchange 有以下 4 种类型,不同的类型对应着不同的路由策略:
+
+#### direct
+
+Exchange 默认类型。**路由规则是把消息路由到 Bindingkey 与 RoutingKey 完全匹配的 Queue 中**。direct 类型常用在**处理有优先级的任务**,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。
+
+
+
+以上图为例,如果发送消息的时候 RoutingKey="booking",那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置 RoutingKey="create" 或 "confirm",消息只会路由到Queue2。如果以其他的 RoutingKey 发送消息,则消息不会路由到这两个队列中。
+
+#### fanout
+
+路由规则是把所有发送到该 Exchange 的消息路由到所有与它绑定的 Queue 中,不需要做任何判断操作,所以 fanout 类型是所有的交换机类型里面速度最快的。**fanout 类型常用来广播消息**。
+
+#### topic
+
+direct 类型的 Exchange 路由规则是完全匹配 BindingKey 和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。
+
+topic 类型的 Exchange 在匹配规则上进行了扩展,它与 direct 类型的 Exchange 相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:
+
+- RoutingKey 为一个点号`.`分隔的字符串,其中**被 `.`分隔开的每一段独立的字符串称为一个单词**;
+- BindingKey 和 RoutingKey 一样也是点号 `.` 分隔的字符串;
+- BindingKey 中可以存在两种特殊字符串 `*` 和 `#`,用于做模糊匹配,其中 `*` 用于匹配一个单词, `#` 用于匹配零个或多个单词。
+
+
+
+以上图为例,如果发送消息的时候 RoutingKey 为
+
+- "com.rabbitmq.client",那么消息会路由到 Queue1 和 Queue2
+- "com.hidden.client",那么消息只会路由到 Queue2 中
+- "com.hidden.demo",那么消息只会路由到 Queue2 中
+- "java.rabbitmq.demo",那么消息只会路由到 Queue1 中
+- "java.util.concurrent",那么消息将会被丢弃或者返回给生产者,因为它没有匹配任何路由键。
+
+#### headers
+
+headers 类型的 Exchange 不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时指定一组键值对,当发送消息到交换器时,RabbitMQ 会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的 Exchange 性能会很差,不推荐使用。
+
+## Queue
+
+Queue 其实是 Message Queue 即消息队列,保存消息并将它们转发给消费者。Queue 是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其消费。
+
+**RabbitMQ 中消息只能存储在队列中**,而 Kafka 将消息存储在 **Topic** 中,即该 Topic 对应的 Partition 中。RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
+
+当多个消费者订阅同一个队列时,队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免消息被重复消费。
+
+### 队列属性
+
+Queue 跟 Exchange 共享某些属性,但是队列也有一些另外的属性:
+
+- Name
+- Durable:消息代理重启后,队列依旧存在
+- Exclusive:只被一个连接使用,而且当连接关闭后队列即被删除
+- Auto-delete:当最后一个消费者退订后即被删除
+- Arguments:一些消息代理用他来完成类似与 TTL 的某些额外功能
+
+### 队列创建
+
+队列在声明(declare)后才能被使用。
+
+如果一个队列尚不存在,声明一个队列会创建它。如果声明的队列已经存在,并且属性完全相同,那么此次声明不会对原有队列产生任何影响。如果声明中的属性与已存在队列的属性有差异,那么一个错误代码为 406 的通道级异常就会被抛出。
+
+### 队列持久化
+
+持久化队列(Durable Queues)会被存储在磁盘上,当消息代理(Broker)重启的时候,它依旧存在。没有被持久化的队列称作暂存队列(Transient Queues)。并不是所有的场景和案例都需要将队列持久化。
+
+持久化的队列并不会使得路由到它的消息也具有持久性。倘若消息代理挂掉了,重新启动,那么在重启的过程中持久化队列会被重新声明,无论怎样,**只有经过持久化的消息才能被重新恢复**。
+
+## 消息机制
+
+### 消息确认
+
+AMQP 代理在什么时候删除消息才是正确的?AMQP 0-9-1 规范给我们两种建议:
+
+- **自动确认模式**:当消息代理(Broker)将消息发送给应用后立即删除。(使用 AMQP 方法:basic.deliver 或 basic.get-ok)
+
+- **显示确认模式**:待 Consumer 发送一个确认回执(acknowledgement)后再删除消息。(使用 AMQP 方法:basic.ack)
+
+ 如果一个消费者在尚未发送确认回执的情况下挂掉了,那 AMQP 代理会将消息重新投递给另一个消费者。如果当时没有可用的消费者了,消息代理会死等下一个注册到此队列的消费者,然后再次尝试投递。
+
+### 拒绝消息
+
+当拒绝某条消息时,应用可以告诉消息代理销毁该条消息或者重新将该条消息放入队列。
+
+当此队列只有一个消费者时,有可能存在拒绝消息并将消息重新放入队列的行为而引起消息在同一个消费者身上无限循环的情况。
+
+### 预取消息
+
+在多个消费者共享一个队列时,明确指定在收到下一个确认回执前每个消费者一次可以接受多少条消息是非常有用的。这可以在试图批量发布消息的时候起到**简单的负载均衡和提高消息吞吐量**的作用。
+
+### 消息属性
+
+AMQP 模型中的消息(Message)对象是带有属性(Attributes)的:
+
+| 属性 | 说明 |
+| --------------------------------- | ------------------------------ |
+| Content type | 内容类型 |
+| Content encoding | 内容编码 |
+| **Routing key** | 路由键 |
+| Delivery mode (persistent or not) | 投递模式(持久化 或 非持久化) |
+| Message priority | 消息优先权 |
+| Message publishing timestamp | 消息发布的时间戳 |
+| Expiration period | 消息有效期 |
+| Publisher application id | 发布应用的 ID |
+
+有些属性是被 AMQP 代理所使用的,但是大多数是开放给接收它们的应用解释器用的。有些属性是可选的也被称作消息头(headers)。和 HTTP 协议的 X-Headers 很相似,消息属性需要在消息被发布的时候定义。
+
+### 消息主体
+
+AMQP 的消息除属性外,也含有一个有效载荷 Payload(消息实际携带的数据),它被 AMQP 代理当作不透明的字节数组来对待。
+
+消息代理不会检查或者修改 Payload,消息可以只包含属性而不携带有效载荷,它通常会使用类似 JSON 这种序列化的格式数据。
+
+### 消息持久化
+
+消息能够以持久化的方式发布,AMQP 代理会将此消息存储在磁盘上。如果服务器重启,系统会确认收到的持久化消息未丢失。
+
+简单地将消息发送给一个持久化的交换机或者路由给一个持久化的队列,并不会使得此消息具有持久化性质:它完全取决与消息本身的持久模式(persistence mode)。将消息以持久化方式发布时,会对性能造成一定的影响(就像数据库操作一样,健壮性的存在必定造成一些性能损失)。
+
+# 三、RabbitMQ 命令行操作
+
+
+
+## 启动 & 停止服务器
+
+- 启动服务器
+
+ ```html
+ rabbitmq-server start &
+ ```
+
+- 停止服务器
+
+ ```html
+ rabbitmqctl stop_app
+ ```
+
+## 查看管控台
+
+```html
+http://localhost:15672/
+
+# 用户名 guest
+# 密码 guest
+```
+
+
+
+## 命令行基础操作
+
+### 1. 应用
+
+- 关闭应用
+
+ ```html
+ rabbitmqctl stop_app
+ ```
+
+- 启动应用
+
+ ```html
+ rabbitmqctl start_app
+ ```
+
+- 查看节点状态
+
+ ```html
+ rabbitmqctl status
+ ```
+
+### 2. 用户
+
+- 添加用户
+
+ ```html
+ rabbitmqctl add_user username password
+ ```
+
+- 删除用户
+
+ ```html
+ rabbitmqctl delete_user username
+ ```
+
+- 列出所有用户
+
+ ```html
+ rabbitmqctl list_users
+ ```
+
+- 清除用户权限
+
+ ```html
+ rabbitmqctl clear_permissions -p vhostpath username
+ ```
+
+- 列出用户权限
+
+ ```html
+ rabbitmqctl list_user_permissions username
+ ```
+
+- 修改密码
+
+ ```html
+ rabbitmqctl change_password username newpassword
+ ```
+
+- 设置用户权限
+
+ ```html
+ rabbitmqctl set_permissions -p vhostpath username ".*" ".*" ".*"
+ ```
+
+### 3. 虚拟主机
+
+- 创建虚拟主机
+
+ ```html
+ rabbitmqctl add_vhost vhostpath
+ ```
+
+- 删除虚拟主机
+
+ ```html
+ rabbitmqctl delete_vhost vhostpath
+ ```
+
+- 列出所有虚拟主机
+
+ ```html
+ rabbitmqctl list_vhosts
+ ```
+
+- 列出虚拟主机上所有权限
+
+ ```html
+ rabbitmqctl list_permissions -p vhostpath
+ ```
+
+### 4. 队列
+
+- 查看所有队列信息
+
+ ```html
+ rabbitmqctl list_queues
+ ```
+
+- 清除队列里的消息
+
+ ```html
+ rabbitmqctl -p vhostpath purge_queue blue
+ ```
+
+
+
+## 命令行高级操作
+
+- 移除所有数据
+
+ ```html
+ rabbitmqctl reset
+ # 要在 rabbitmqctl stop_app 之后使用
+ ```
+
+- 组成集群命令
+
+ ```html
+ rabbitmqctl join_cluster [--ram]
+ ```
+
+- 查看集群状态
+
+ ```html
+ rabbitmq cluster_status
+ ```
+
+- 修改集群节点的存储形式
+
+ ```html
+ rabbitmqctl change_cluser_node_type disc | ram
+ ```
+
+- 摘除节点(忘记节点)
+
+ ```html
+ rabbitmqctl forget_cluster_node [--offline]
+ ```
+
+- 修改节点名称
+
+ ```html
+ rabbitmqctl rename_cluster_node oldnode1 newnode1 [oldnode2] [newnode2]
+ ```
+
+# 四、Rabbit MQ 入门
+
+## 简单案例:消息生产与消费
+
+pom.xml 配置
+
+```html
+
+ com.rabbitmq
+ amqp-client
+
+ 3.6.5
+
+```
+
+### 生产者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 简单案例:消息生产与消费
+ * 消息生产者
+ * Created by DHA on 2019/11/18.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 通过 chanel 发送数据
+ for(int i=0;i<10;i++){
+ String data="Hello!";
+ channel.basicPublish("","test001",null,data.getBytes());
+ }
+
+ //5 关闭相关连接
+ channel.close();
+ connection.close();
+ }
+}
+```
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 简单案例:消息生产与消费
+ * 消息生产者
+ * Created by DHA on 2019/11/18.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 通过 chanel 发送数据
+ for(int i=0;i<10;i++){
+ String data="Hello!";
+ channel.basicPublish("","test001",null,data.getBytes());
+ }
+
+ //5 关闭相关连接
+ channel.close();
+ connection.close();
+ }
+}
+```
+
+## Direct Exchange
+
+### 生产者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Direct Exchange
+ * 所有发送到 Direct Exchange 的消息被转发到 routing key 中指定的 Queue。
+ * 消息生产者
+ * Created by DHA on 2019/11/19.
+ */
+public class Producer4DirectExchange {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ //声明 exchange 名称
+ String exchangeName="test_direct_exchange";
+ String routingKey = "test.direct";
+
+ //5 通过 chanel 发送数据
+ String msg = "Hello World RabbitMQ 4 Direct Exchange Message ... ";
+ channel.basicPublish(exchangeName, routingKey , null , msg.getBytes());
+
+ //6 关闭相关连接
+ channel.close();
+ connection.close();
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Direct Exchange
+ * 所有发送到 Direct Exchange 的消息被转发到 routing key 中指定的 Queue。
+ * 消息消费者
+ * Created by DHA on 2019/11/19.
+ */
+public class Consumer4DirectExchange {
+ public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName="test_direct_exchange";
+ String exchangeType="direct";
+ String queueName="test_direct_queue";
+ String routingKey="test.direct";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ //5 创建消费者
+ QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
+
+ //6 设置 channel
+ channel.basicConsume(queueName,true,queueingConsumer);
+
+ //7 获取数据
+ while(true){
+ QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
+ String msg=new String(delivery.getBody());
+ System.out.println("消费端:"+msg);
+ }
+ }
+}
+```
+
+
+
+## Topic Exchange
+
+### 生产者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Topic Exchange
+ * Topic Exchange 将 routing key 与某 Topic 进行模糊匹配,此时队列需要绑定一个 Topic。
+ * 消息生产者
+ * Created by DHA on 2019/11/19.
+ */
+public class Producer4TopicExchange {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ //声明 exchange 名称
+ String exchangeName="test_topic_exchange";
+ String routingKey1 = "user.save";
+ String routingKey2 = "user.update";
+ String routingKey3 = "user.delete.abc";
+
+ //5 通过 chanel 发送数据
+ String msg = "Hello World RabbitMQ 4 Topic Exchange Message ... ";
+ channel.basicPublish(exchangeName, routingKey1 , null , msg.getBytes());
+ channel.basicPublish(exchangeName, routingKey2 , null , msg.getBytes());
+ channel.basicPublish(exchangeName, routingKey3 , null , msg.getBytes());
+
+ //6 关闭相关连接
+ channel.close();
+ connection.close();
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Topic Exchange
+ * Topic Exchange 将 routing key 与某 Topic 进行模糊匹配,此时队列需要绑定一个 Topic。
+ * 消息消费者
+ * Created by DHA on 2019/11/19.
+ */
+public class Consumer4TopicExchange {
+ public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName="test_topic_exchange";
+ String exchangeType="topic";
+ String queueName="test_topic_queue";
+ String routingKey="user.*";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ //5 创建消费者
+ QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
+
+ //6 设置 channel
+ channel.basicConsume(queueName,true,queueingConsumer);
+
+ //7 获取数据
+ while(true){
+ QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
+ String msg=new String(delivery.getBody());
+ System.out.println("消费端:"+msg);
+ }
+ }
+}
+```
+
+
+
+## Fanout Exchange
+
+### 生产者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Fanout Exchange
+ * Fanout Exchange 不处理 routing key,只需要简单的将队列绑定到交换机上,发送到交换机的消息都会被转发到交换机绑定的所有队列上。
+ * 消息生产者
+ * Created by DHA on 2019/11/19.
+ */
+public class Producer4FanoutExchange {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ //声明 exchange 名称
+ String exchangeName="test_fanout_exchange";
+
+ //5 通过 chanel 发送数据
+ for(int i = 0; i < 10; i ++) {
+ String msg = "Hello World RabbitMQ 4 Fanout Exchange Message ...";
+ channel.basicPublish(exchangeName, "", null , msg.getBytes());
+ }
+
+ //6 关闭相关连接
+ channel.close();
+ connection.close();
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Fanout Exchange
+ * Fanout Exchange 不处理 routing key,只需要简单的将队列绑定到交换机上,发送到交换机的消息都会被转发到交换机绑定的所有队列上。
+ * 消息消费者
+ * Created by DHA on 2019/11/19.
+ */
+public class Consumer4FanoutExchange {
+ public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName="test_fanout_exchange";
+ String exchangeType="fanout";
+ String queueName="test_fanout_queue";
+ String routingKey="";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ //5 创建消费者
+ QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
+
+ //6 设置 channel
+ channel.basicConsume(queueName,true,queueingConsumer);
+
+ //7 获取数据
+ while(true){
+ QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
+ String msg=new String(delivery.getBody());
+ System.out.println("消费端:"+msg);
+ }
+ }
+}
+```
+
+
+
+## 设置消息属性
+
+### 生产者
+
+```java
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 消息属性设置
+ * 消息生产者
+ * Created by DHA on 2019/11/18.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ // 设置自定义属性
+ Map headers = new HashMap<>();
+ headers.put("attr1", "111");
+ headers.put("attr2", "222");
+
+ //4 设置消息属性
+ AMQP.BasicProperties properties=new AMQP.BasicProperties.Builder()
+ .deliveryMode(2) // 2 表示持久化的投递
+ .contentEncoding("UTF-8") // 设置内容编码
+ .expiration("10000") // 设置过期时间为 10 秒
+ .headers(headers) // 自定义属性
+ .build();
+
+ //5 通过 chanel 发送数据
+ for(int i=0;i<5;i++){
+ String data="Hello!";
+ channel.basicPublish("","test001",properties,data.getBytes());
+ }
+
+ //6 关闭相关连接
+ channel.close();
+ connection.close();
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+import com.rabbitmq.client.QueueingConsumer.Delivery;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 消息属性设置
+ * 消息消费者
+ * Created by DHA on 2019/11/18.
+ */
+public class Consumer {
+ public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明一个队列
+ String queueName="test001";
+ channel.queueDeclare(queueName,true,false,false,null);
+
+ //5 创建消费者
+ QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
+
+ //6 设置 channel
+ channel.basicConsume(queueName,true,queueingConsumer);
+
+ //7 获取数据
+ while(true){
+ Delivery delivery=queueingConsumer.nextDelivery();
+ String msg=new String(delivery.getBody());
+ System.out.println("消费端:"+msg);
+ // 获取自定义属性数据
+ Map headers=delivery.getProperties().getHeaders();
+ System.err.println("headers get attribute attr1 value: " + headers.get("attr1"));
+ }
+ }
+}
+```
+
+
+
+# 五、RabbitMQ 高级特性
+
+## 消息100%可靠性投递的解决方案
+
+### 生产端可靠性投递
+
+- 保障消息成功发出
+- 保障 MQ 节点的成功接收
+- 发送端收到 MQ 节点(Broker)确认应答
+- **完善的消息补偿机制**
+
+### 解决方案1:消息落库
+
+消息落库,对消息状态进行打标。
+
+
+
+
+
+### 解决方案2:二次确认,回调检查
+
+消息的延迟投递,做二次确认,回调检查。
+
+
+
+
+
+## 消费端幂等性操作
+
+- 唯一 ID + 指纹码 机制,利用数据库主键去重
+
+ 优点:实现简单
+
+ 缺点:高并罚下有数据库写入的性能瓶颈
+
+ 解决方案:根据 ID 进行分库分表进行算法路由
+
+- 利用 Redis 原子特性实现
+
+## Confirm 消息机制
+
+消息的确认是指生产者投递消息后,如果 Broker 收到消息,则会给生产者一个应答,生产者进行接收应答,用来确定这条消息是否正常地发送到 Broker。
+
+
+
+实现机制:
+
+- 第一步:在 channel 上开启确认模式
+
+ ```java
+ channel.confirmSelect()
+ ```
+
+- 第二步:在 channel 上添加监听
+
+ ```java
+ channel.addConfirmListener()
+ ```
+
+ 监听成功和失败的返回结果,根据具体的结果对消息进行重新发送或记录日志等后续处理。
+
+### 生产者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.ConfirmListener;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Confirm 消息机制
+ * 消息的确认是指生产者投递消息后,如果 Broker 收到消息,则会给生产者一个应答,生产者进行接收应答,用来确定这条消息是否正常地发送到 Broker。
+ * 消息生产者
+ * Created by DHA on 2019/11/18.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 指定消息投递模式:confirm 模式
+ channel.confirmSelect();
+
+ String exchangeName = "test_confirm_exchange";
+ String routingKey = "confirm.save";
+
+ //5 通过 chanel 发送数据
+ String msg="Hello!";
+ channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
+
+ //6 添加一个确认监听
+ channel.addConfirmListener(new ConfirmListener() {
+ @Override
+ public void handleAck(long l, boolean b) throws IOException {
+ System.out.println("------ack!-------");
+ }
+
+ @Override
+ public void handleNack(long l, boolean b) throws IOException {
+ System.out.println("------Nack!-------");
+ }
+ });
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+import com.rabbitmq.client.QueueingConsumer.Delivery;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Confirm 消息机制
+ * 消息的确认是指生产者投递消息后,如果 Broker 收到消息,则会给生产者一个应答,生产者进行接收应答,用来确定这条消息是否正常地发送到 Broker。
+ * 消息消费者
+ * Created by DHA on 2019/11/18.
+ */
+public class Consumer {
+ public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName="test_confirm_exchange";
+ String exchangeType="topic";
+ String queueName="test_confirm_queue";
+ String routingKey="confirm.#";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ //5 创建消费者
+ QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
+
+ //6 设置 channel
+ channel.basicConsume(queueName,queueingConsumer);
+
+ //7 获取数据
+ while(true){
+ Delivery delivery=queueingConsumer.nextDelivery();
+ String msg=new String(delivery.getBody());
+ System.out.println("消费端:"+msg);
+ }
+ }
+}
+```
+
+
+
+## Return 消息机制
+
+消息生产者通过制动一个 Exchange 和 routing key,把消息送达到某一个队列中去,然后消费者监听队列,进行消费处理操作。
+
+在某些情况下,如果我们在发送消息的时候,当前的 **Exchange 不存在**或者指定的 **routing key路由不到**,此时我们需要监听这种不可达的消息,就要使用 Return Listener。
+
+基础 API 有一个配置项 mandatory
+
+- 如果为 true,那么监听器会接收到路由不可达的消息,然后进行后续处理
+- 如果为 false, 那么 Broker 端自动删除该消息
+
+
+
+
+
+
+
+### 生产者
+
+```java
+import com.rabbitmq.client.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Return 消息机制
+ * 消息生产者通过制动一个 Exchange 和 routing key,把消息送达到某一个队列中去,然后消费者监听队列,进行消费处理操作。
+ * 在某些情况下,如果我们在发送消息的时候,当前的 Exchange 不存在或者指定的 routing key路由不到,此时我们需要监听这种不可达的消息,就要使用 Return Listener。
+ *
+ * 消息生产者
+ * Created by DHA on 2019/11/18.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 指定消息投递模式:confirmListener 模式
+ channel.confirmSelect();
+
+ String exchangeName = "test_return_exchange";
+ String routingKey = "returnListener.save";
+ String routingKeyError = "return.save";
+
+ //5 通过 chanel 发送数据
+ String msg="Hello!";
+ // mandatory 如果为 true,那么监听器会接收到路由不可达的消息,然后进行后续处理
+ // mandatory 如果为 false, 那么 Broker 端自动删除该消息
+ channel.basicPublish(exchangeName,routingKeyError,true,null,msg.getBytes());
+
+ //6 添加一个监听
+ channel.addReturnListener(new ReturnListener() {
+ @Override
+ public void handleReturn(int replyCode, String replyText, String exchange,
+ String routingKey, AMQP.BasicProperties properties, byte[] body)
+ throws IOException {
+ System.err.println("---------handle return----------");
+ System.err.println("replyCode: " + replyCode);
+ System.err.println("replyText: " + replyText);
+ System.err.println("exchange: " + exchange);
+ System.err.println("routingKey: " + routingKey);
+ System.err.println("properties: " + properties);
+ System.err.println("body: " + new String(body));
+ }
+ });
+
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+import com.rabbitmq.client.QueueingConsumer.Delivery;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Return 消息机制
+ * 消息生产者通过制动一个 Exchange 和 routing key,把消息送达到某一个队列中去,然后消费者监听队列,进行消费处理操作。
+ * 在某些情况下,如果我们在发送消息的时候,当前的 Exchange 不存在或者指定的 routing key路由不到,此时我们需要监听这种不可达的消息,就要使用 Return Listener。
+ *
+ * 消息消费者
+ * Created by DHA on 2019/11/18.
+ */
+public class Consumer {
+ public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName="test_return_exchange";
+ String exchangeType="topic";
+ String queueName="test_return_queue";
+ String routingKey="returnListener.#";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ //5 创建消费者
+ QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
+
+ //6 设置 channel
+ channel.basicConsume(queueName,queueingConsumer);
+
+ //7 获取数据
+ while(true){
+ Delivery delivery=queueingConsumer.nextDelivery();
+ String msg=new String(delivery.getBody());
+ System.out.println("消费端:"+msg);
+ }
+ }
+}
+```
+
+
+
+## 消费端自定义监听
+
+我们一般在代码中编写 while 循环,进行 consumer.nextDelivery 方法获取下一条消息,然后进行消费处理!
+
+但是,我们使用自定义的 Counsumer 更加方便,解耦性更强,在实际工作中广泛使用。
+
+### 自定义消费者
+
+实现步骤:
+
+- 先继承 `com.rabbitmq.client.DefaultConsumer`
+- 再重写 `handleDelivery()` 方法
+
+```java
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.DefaultConsumer;
+import com.rabbitmq.client.Envelope;
+
+import java.io.IOException;
+
+/**
+ * 自定义消费者
+ * 1 先继承 DefaultConsumer
+ * 2 然后重写 handleDelivery() 方法
+ *
+ * Created by DHA on 2019/11/20.
+ */
+public class MyConsumer extends DefaultConsumer{
+ public MyConsumer(Channel channel) {
+ super(channel);
+ }
+
+ @Override
+ public void handleDelivery(String consumerTag, Envelope envelope,
+ AMQP.BasicProperties properties, byte[] body)
+ throws IOException {
+ System.err.println("----------consumer message-----------");
+ System.err.println("consumerTag:"+consumerTag);
+ System.err.println("envelope:"+envelope);
+ System.err.println("properties:"+properties);
+ System.err.println("body:"+new String(body));
+ }
+}
+```
+
+
+
+### 生产者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 消费端自定义监听
+ * 消息生产者
+ * Created by DHA on 2019/11/19.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ //声明 exchange 名称
+ String exchangeName="test_consumer_exchange";
+ String routingKey = "consumer.save";
+
+ //5 通过 chanel 发送数据
+ String msg = "Hello World RabbitMQ 4 Consumer Exchange Message ... ";
+ channel.basicPublish(exchangeName, routingKey , true,null , msg.getBytes());
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+import com.rabbitmq.client.QueueingConsumer.Delivery;
+
+public class Consumer {
+
+ public static void main(String[] args) throws Exception {
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName = "test_consumer_exchange";
+ String exchangeType= "topic";
+ String routingKey = "consumer.#";
+ String queueName = "test_consumer_queue";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ /*
+ //5 创建消费者
+ QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
+
+ //6 设置 channel
+ channel.basicConsume(queueName,true,queueingConsumer);
+
+ //7 获取数据
+ while(true){
+ QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
+ String msg=new String(delivery.getBody());
+ System.out.println("消费端:"+msg);
+ }
+ */
+
+ //5 消费端自定义监听 使用 MyConsumer 相应实例
+ channel.basicConsume(queueName, true, new MyConsumer(channel));
+ }
+}
+```
+
+
+
+## 消费端限流
+
+RabbitMQ 提供了一种 QoS(服务质量保证) 功能,**在非自动确认消息的前提下**,如果一定数目的消息(通过基于 Consume 或者 Channel 设置 QoS 值)未被确认前,不进行消费新的消息。
+
+涉及到的方法:
+
+```erlang
+void BasicQoS(unit prefetchSize,ushort prefetchCount,bool global)
+```
+
+- prefetchSize:0
+- prefetchCount:告知 RabbitMQ 不要同时给一个消费者推送多个 N 个消息,即一旦有 N 个消息还没有 ACK,则该 Consumer 将 block 掉,一直到有消息 ack
+- golbal:true 表示将上面设置应用于 Channel;true 表示将上面设置应用于 Consumer。
+
+注意:
+
+- prefetchSize 和 global 这两项,RabbitMQ 没有实现,暂且不研究
+- prefetchCount 在 no_ask-false 的情况下生效,即在自动应答的情况下是不生效的
+
+### 生产者
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 消费端限流
+ * 消息生产者
+ * Created by DHA on 2019/11/19.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ //声明 exchange 名称
+ String exchangeName="test_qos_exchange";
+ String routingKey = "qos.save";
+
+ //5 通过 chanel 发送数据
+ for(int i=0;i<5;i++){
+ String msg = "Hello World RabbitMQ 4 Qos Message ... ";
+ channel.basicPublish(exchangeName, routingKey , true,null , msg.getBytes());
+ }
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.DefaultConsumer;
+import com.rabbitmq.client.Envelope;
+
+import java.io.IOException;
+
+/**
+ * 自定义消费者
+ * 1 先继承 DefaultConsumer
+ * 2 然后重写 handleDelivery() 方法
+ *
+ * Created by DHA on 2019/11/20.
+ */
+public class MyConsumer extends DefaultConsumer{
+
+ // channel 进行签收
+ private Channel channel;
+
+ public MyConsumer(Channel channel) {
+ super(channel);
+ this.channel=channel;
+ }
+
+ @Override
+ public void handleDelivery(String consumerTag, Envelope envelope,
+ AMQP.BasicProperties properties, byte[] body)
+ throws IOException {
+ System.err.println("----------consumer message-----------");
+ System.err.println("consumerTag:"+consumerTag);
+ System.err.println("envelope:"+envelope);
+ System.err.println("properties:"+properties);
+ System.err.println("body:"+new String(body));
+
+ // false 表示不支持批量签收
+ channel.basicAck(envelope.getDeliveryTag(),false);
+ }
+}
+```
+
+
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+/**
+ * 消费端限流
+ * 消息消费者
+ *
+ * basicQoS(prefetchSize,refetchCount,global)
+ * - prefetchSize:0
+ * - prefetchCount:告知 RabbitMQ 不要同时给一个消费者推送多个 N 个消息,即一旦有 N 个消息还没有 ACK,
+ * 则该 Consumer 将 block 掉,一直到有消息 ack
+ * - golbal:true 表示将上面设置应用于 Channel;true 表示将上面设置应用于 Consumer。
+ *
+ * Created by DHA on 2019/11/19.
+ */
+public class Consumer {
+
+ public static void main(String[] args) throws Exception {
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName = "test_qos_exchange";
+ String exchangeType= "topic";
+ String routingKey = "qos.#";
+ String queueName = "test_qos_queue";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ // 第二个参数为 1,表示一次处理一条消息
+ // 第三个参数为 false,表示应用到 Consumer 级别
+ channel.basicQos(0,1,false);
+
+ //5 消费端自定义监听
+ // 首先将第二个参数设置为 false,进行手动签收
+ channel.basicConsume(queueName, false, new MyConsumer(channel));
+ }
+}
+```
+
+
+
+## 消费端 ACK 与重回队列
+
+- **消费端的手工 ACK 和 NACK**
+
+ 消费端进行消费时:
+
+ 如果由于业务异常,我们可以进行日志的记录,然后进行补偿;
+
+ 如果由于服务器宕机等严重问题,那么需要手工进行 ACK 保障消费端消费成功
+
+- **消费端的重回队列**
+
+ 消费端重回队列是为了对没有成功的消息, 消息会被重新投递给 Broker。一般在使用应用中,都会关闭重回队列,即设置为 false。
+
+### 生产者
+
+```java
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 消费端的手工 ACK 和 NACK
+ * 消息生产者
+ *
+ * Created by DHA on 2019/11/19.
+ */
+public class Producer {
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ //声明 exchange 名称
+ String exchangeName="test_ack_exchange";
+ String routingKey = "ack.save";
+
+ //5 通过 chanel 发送数据
+ for(int i =0; i<5; i ++){
+
+ Map headers = new HashMap();
+ headers.put("num", i);
+
+ AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
+ .deliveryMode(2)
+ .contentEncoding("UTF-8")
+ .headers(headers)
+ .build();
+ String msg = "Hello RabbitMQ ACK Message " + i;
+ channel.basicPublish(exchangeName, routingKey, true, properties, msg.getBytes());
+ }
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.DefaultConsumer;
+import com.rabbitmq.client.Envelope;
+
+import java.io.IOException;
+
+/**
+ * 消费端的重回队列
+ * 消费端重回队列是为了对没有成功的消息, 消息会被重新投递给 Broker。
+ * 一般在使用应用中,都会关闭重回队列,即设置为 false。
+ *
+ * Created by DHA on 2019/11/20.
+ */
+public class MyConsumer extends DefaultConsumer{
+
+ // channel 进行签收
+ private Channel channel;
+
+ public MyConsumer(Channel channel) {
+ super(channel);
+ this.channel=channel;
+ }
+
+ @Override
+ public void handleDelivery(String consumerTag, Envelope envelope,
+ AMQP.BasicProperties properties, byte[] body)
+ throws IOException {
+ System.err.println("-----------consume message----------");
+ System.err.println("body: " + new String(body));
+
+ // 为了实验效果明显
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ Integer num=(Integer) properties.getHeaders().get("num");
+ if(num==0){
+ // 第二个参数表示是否支持批量签收,如果为 false,表示不支持批量签收
+ // 第三个参数表示是否重回队列,如果为 true,表示支持重回队列,则会重回到队列的尾端
+ channel.basicNack(envelope.getDeliveryTag(),false,true);
+ }else{
+ // false 表示不支持批量签收
+ channel.basicAck(envelope.getDeliveryTag(),false);
+ }
+ //channel.basicAck(envelope.getDeliveryTag(),false);
+ }
+}
+```
+
+
+
+```java
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+/**
+ * 消费端的手工 ACK 和 NACK
+ * 消息消费者
+ *
+ * Created by DHA on 2019/11/19.
+ */
+public class Consumer {
+
+ public static void main(String[] args) throws Exception {
+ //1 创建一个 Connectionfactory,并进行设置
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ //2 通过连接工厂创建连接
+ Connection connection = connectionFactory.newConnection();
+
+ //3 通过 connecion 创建一个 Channel
+ Channel channel = connection.createChannel();
+
+ //4 声明
+ String exchangeName = "test_ack_exchange";
+ String exchangeType= "topic";
+ String routingKey = "ack.#";
+ String queueName = "test_ack_queue";
+
+ // 声明一个交换机
+ channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
+ // 声明一个队列
+ channel.queueDeclare(queueName,false,false,false,null);
+ // 绑定:将一个队列绑定到一个交换机上
+ channel.queueBind(queueName,exchangeName,routingKey);
+
+ //5 消费端自定义监听
+ // 首先将第二个参数 autoACK 设置为 false,进行手动签收
+ channel.basicConsume(queueName, false, new MyConsumer(channel));
+ }
+}
+```
+
+
+
+## TTL
+
+TTL(Time To Live)即生存时间。
+
+- RabbitMQ 支持**消息**的过期时间,在消息发送时可以进行指定
+- RabbitMQ 支持**队列**的过期时间,从消息如队列开始计算,只要超过了队列的超时时间配置,那么会自动清除消息
+
+## 死信队列(DLX,Dead-Letter-Exchange )
+
+利用 DLX,当消息在一个队列中变成死信(dead message)之后,其能被重新 publish 到另一个 Exchange,这个 Exchange 就是 DLX。
+
+消息变成死信的几种情况:
+
+- 消息被拒绝(basic.reject / basic.nack),并且 requeue=false
+- 消息 TTL 过期
+- 队列达到最大长度
+
+注意:
+
+- DLX 也是一个正常的 Exchange,和一般的 Exchange 没有区别,它能在任何队列上被指定,实际上就是设置某个队列的属性。
+
+- 当这个队列中有死信时,RabbitMQ 就会自动的将这个消息重新发布到设置的 Exchange 上去,进而被路由到另一个队列。
+
+- 死信队列设置需要设置 Exchange 和 队列,然后绑定
+
+ ```java
+ channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
+ channel.queueDeclare("dlx.queue", true, false, false, null);
+ channel.queueBind("dlx.queue", "dlx.exchange", "#");
+ ```
+
+ 然后我们进行正常声明 Exchange、队列和绑定,此时需要在队列上加上参数 arguments
+
+ ```java
+ Map agruments = new HashMap();
+ agruments.put("x-dead-letter-exchange", "dlx.exchange");
+ //这个agruments属性,要设置到声明队列上
+ channel.queueDeclare(queueName, true, false, false, agruments);
+ ```
+
+### 生产者
+
+```java
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+/**
+ * 死信队列
+ * 利用 DLX,当消息在一个队列中变成死信(dead message)之后,
+ * 其能被重新 publish 到另一个 Exchange,这个 Exchange 就是 DLX。
+ *
+ * 消息生产者
+ * Created by DHA on 2019/11/20.
+ */
+public class Producer {
+
+ public static void main(String[] args) throws Exception {
+
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ Connection connection = connectionFactory.newConnection();
+ Channel channel = connection.createChannel();
+
+ String exchange = "test_dlx_exchange";
+ String routingKey = "dlx.save";
+
+ String msg = "Hello RabbitMQ DLX Message";
+
+ AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
+ .deliveryMode(2)
+ .contentEncoding("UTF-8")
+ .expiration("10000")
+ .build();
+ channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
+ }
+}
+```
+
+
+
+### 消费者
+
+```java
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.DefaultConsumer;
+import com.rabbitmq.client.Envelope;
+
+import java.io.IOException;
+
+/**
+ * 自定义消费者
+ * 1 先继承 DefaultConsumer
+ * 2 然后重写 handleDelivery() 方法
+ *
+ * Created by DHA on 2019/11/20.
+ */
+public class MyConsumer extends DefaultConsumer{
+ public MyConsumer(Channel channel) {
+ super(channel);
+ }
+
+ @Override
+ public void handleDelivery(String consumerTag, Envelope envelope,
+ AMQP.BasicProperties properties, byte[] body)
+ throws IOException {
+ System.err.println("----------consumer message-----------");
+ System.err.println("consumerTag:"+consumerTag);
+ System.err.println("envelope:"+envelope);
+ System.err.println("properties:"+properties);
+ System.err.println("body:"+new String(body));
+ }
+}
+```
+
+
+
+```java
+import java.util.HashMap;
+import java.util.Map;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+/**
+ * 死信队列
+ * 利用 DLX,当消息在一个队列中变成死信(dead message)之后,
+ * 其能被重新 publish 到另一个 Exchange,这个 Exchange 就是 DLX。
+ *
+ * 消息消费者
+ * Created by DHA on 2019/11/20.
+ */
+public class Consumer {
+
+ public static void main(String[] args) throws Exception {
+
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost("localhost");
+ connectionFactory.setPort(5672);
+ connectionFactory.setVirtualHost("/");
+
+ Connection connection = connectionFactory.newConnection();
+ Channel channel = connection.createChannel();
+
+ // 这就是一个普通的交换机 和 队列 以及路由
+ String exchangeName = "test_dlx_exchange";
+ String routingKey = "dlx.#";
+ String queueName = "test_dlx_queue";
+
+ channel.exchangeDeclare(exchangeName, "topic", true, false, null);
+ Map agruments = new HashMap();
+ agruments.put("x-dead-letter-exchange", "dlx.exchange");
+ //这个agruments属性,要设置到声明队列上
+ channel.queueDeclare(queueName, true, false,false,agruments);
+ channel.queueBind(queueName, exchangeName, routingKey);
+
+ //要进行死信队列的声明:
+ channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
+ channel.queueDeclare("dlx.queue", true, false, false, null);
+ channel.queueBind("dlx.queue", "dlx.exchange", "#");
+
+ channel.basicConsume(queueName, true, new MyConsumer(channel));
+ }
+}
+```
+
diff --git "a/docs/Kafka/2_Kafka\347\232\204\346\236\266\346\236\204.md" "b/docs/Kafka/2_Kafka\347\232\204\346\236\266\346\236\204.md"
deleted file mode 100644
index 87998f60..00000000
--- "a/docs/Kafka/2_Kafka\347\232\204\346\236\266\346\236\204.md"
+++ /dev/null
@@ -1,164 +0,0 @@
-# Kafka的架构
-
-## Kafka 的基本组成
-
-Kafka 集群由若干个 Broker 组成,Topic 由若干个 Partition 组成,每个 Partition 里面的消息通过 Offset 来获取。
-
-### 1. Message
-
-消息(Message)是 Kafka 中**最基本的数据单元**。
-
-Kafka 消息由一个**定长的 Header 和变长的字节数组**组成,其中主要由 key 和 value 构成,key 和 value 也都是字节数组。
-
-
-
-### 2. Broker
-
-Kafka 集群包含一个或多个服务器,这种服务器被称为 Broker。
-
-
-
-### 3. Topic
-
-Kafka 根据主题(Topic)对消息进行归类,**发布到 Kafka 集群的每条消息(Message)都需要指定一个 Topic**。
-
-
-
-### 4. Partition
-
-**物理概念**,每个 Topic 包含一个或多个分区(Partition)。
-
-消息发送时都被发送到一个 Topic,其本质就是一个目录,而 Topic 由是由一些 Partition Logs(分区日志)组成,其组织结构如下:
-
-
-
-**每个 Partition 中的消息都是有序的**,生产的消息被不断追加到 Partition Log 上,其中的每一个消息都被赋予了一个唯一的 offset 值,Kafka 通过 **offset 保证消息在分区内的顺序**,offset 的顺序性不跨分区,即 Kafka 只保证在同一个分区内的消息是有序的;同一 Topic 的多个分区内的消息,Kafka 并不保证其顺序性。
-
-**Kafka 集群会保存所有的消息,不管消息有没有被消费**;
-
-我们可以设定消息的过期时间,只有过期的数据才会被自动清除以释放磁盘空间。比如我们设置消息过期时间为 2 天,那么这 2天内的所有消息都会被保存到集群中,数据只有超过了两天才会被清除。
-
-Kafka 需要维持的元数据只有一个,即消费消息在 Partition 中的 offset 值,Consumer 每消费一个消息,offset就会 +1。其实消息的状态完全是由 Consumer 控制的,**Consumer 可以跟踪和重设这个 offset 值,Consumer 就可以读取任意位置的消息**。
-
-把消息日志以 Partition 的形式存放有多重考虑:
-
-- 第一,方便在集群中扩展,每个 Partition 可以通过调整以适应它所在的机器,而一个 Topic 又可以由多个Partition 组成,因此整个集群就可以适应任意大小的数据了;
-- 第二,就是可以提高并发,因为是以 Partition 为单位进行读写。
-
-注意:Partition 并不是越多越好的
-
-原因:
-
-- 分区越多,服务端和客户端需要使用的内存就越多
-- 会降低一定的可用性。某个 Leader 挂了,相比较较少分区的情况,重新选出 Leader,花的时间就会更长。
-
-
-
-### 5. Replication
-
-Kafka 中每个 Partition 可以有多个副本(Replication),每个副本中包含的消息是一样的。
-
-每个分区的副本集合中,都会选举出一个副本作为 Leader 副本,Kafka 在不同的场景下会采用不同的选举策略。所有的读写请求都由选举出的 Leader 副本处理,其他都作为 Follower 副本,**Follower 副本仅仅是从 Leader 副本处把数据拉取(pull)到本地之后,同步更新到自己的 Log 中**。
-
-一般情况下,同一分区的多个副本会被分配到不同的 Broker上。当 Leader 所在的 Broker 宕机之后,可以重新选举新的 Leader,继续对外提供服务。
-
-
-
-
-
-### 6. Producer
-
-消息生产者(Producer),向 Broker 发送消息的客户端。
-
-Producer 直接发送消息到 Broker上的 Leader Partition,不需要经过任何中介或其他路由转发。
-
-**Producer 客户端自己控制着消息被推送(push)到哪些 Partition**。实现方式可以是随机分配、实现一类随机负载均衡算法,或者指定一些分区算法。Kafka 提供了接口供用户实现自定义的 Partition,用户可以为每个消息指定一个 Partition Key,通过这个 key 来实现一些 Hash 分区算法。比如,把 userid 作为 Partition Key 的话,相同 userid 的消息将会被推送到同一个 Partition。
-
-Kafka Producer 可以将消息在内存中累计到一定数量后作为一个**批量发送请求**。批量发送的数量大小可以通过Producer 的参数控制,参数值可以设置为累计的消息的数量(如 500 条)、累计的时间间隔(如 100ms )或者累计的数据大小(64 KB)。通过增加 Batch的大小,可以减少网络请求和磁盘 I / O 的次数,当然具体参数设置需要在**效率**和**时效性**方面做一个权衡。
-
-Producer 可以**异步地并行地**向 Kafka发送消息,但是通常 Producer 在发送完消息之后会得到一个 future响应,返回的是 offset 值或者发送过程中遇到的错误。通过 request.required.acks 参数来设置 Leader Partition 收到确认的副本个数:
-
-| ack | 说明 |
-| :--: | :----------------------------------------------------------: |
-| 0 | Producer **不会等待 Broker 的响应**
Producer 无法知道消息是否发送成功, 这样**可能会导致数据丢失**,但会得到最大的系统吞吐量。 |
-| 1 | Producer 会在 **Leader Partition** 收到消息时得到 Broker 的一个确认
这样会有更好的可靠性,因为客户端会等待直到 Broker 确认收到消息。 |
-| -1 | Producer 会在**所有备份的 Partition** 收到消息时得到 Broker 的确认
这个设置可以得到最高的可靠性保证。 |
-
-发布消息时,Kafka Client 先构造一条消息,将消息加入到消息集 set 中(Kafka支持批量发布,可以往消息集合中添加多条消息,一次行发布),send 消息时,Producer Client 需指定消息所属的 Topic。
-
-
-
-### 7. Consumer
-
-消息消费者(Consumer),从 Broker 读取消息的客户端。
-
-消费者(Consumer)的主要工作是从 Topic 中拉取消息,并对消息进行消费。某个消费者消费到 Partition 的哪个位置(offset)的相关信息,是 Consumer 自己维护的。Consumer 可以自己决定如何读取 Kafka 中的数据。比如,Consumer 可以通过重设 offset 值来重新消费已消费过的数据。不管有没有被消费,Kafka 会保存数据一段时间,这个时间周期是可配置的,只有到了过期时间,Kafka 才会删除这些数据。
-
-这样设计非常巧妙,**避免了 Kafka Server 端维护消费者消费位置的开销**,尤其是在消费数量较多的情况下。另一方面,如果是由 Kafka Server 端管理每个 Consumer 消费状态,一旦 Kafka Server 端出现延时或是消费状态丢失,将会影响大量的 Consumer。另一方面,这一设计也提高了 Consumer 的灵活性,Consumer 可以按照自己需要的顺序和模式拉取消息进行消费。例如:Consumer 可以通过修改其消费的位置实现针对某些特殊 key 的消息进行反复消费,或是跳过某些消息的需求。
-
-Kafka 提供了两套 Consumer Api,分为 Simple Api 和 High-Level Api。
-
-- Simple Api 是一个底层的 API,它维持了一个和单一 Broker 的连接,并且这个 API 是完全无状态的,每次请求都需要指定 offset 值,因此,这套 API 也是最灵活的。
-
-- High-Level API 封装了对集群中一系列 Broker 的访问,可以透明的消费一个 Topic。它自己维持了已消费消息的状态,即每次消费的都是下一个消息。
-
- High-Level API 还支持以组的形式消费 Topic,如果 Consumers 有同一个组名,那么 Kafka 就相当于一个队列消息服务,而各个 Consumer 均衡地消费相应 Partition 中的数据。若 Consumers 有不同的组名,那么此时 Kafka 就相当于一个广播服务,会把 Topic 中的所有消息广播到每个 Consumer。
-
- 
-
-
-
-### 8. Consumer Group
-
-在 Kafka 中,多个 Consumer 可以组成一个 Consumer Group,一个 Consumer 只能属于一个 Consumer Group。**Consumer Group 保证其订阅的 Topic 的每个 Partition 只会被此 Consumer Group 中的一个 Consumer 处理**。如果不同 Consumer Group 订阅了同一 Topic,Consumer Group 彼此之间不会干扰。这样,如果要实现一个消息可以被多个消费者同时消费(“广播”)的效果,则将每个消费者放入单独的一个 Consumer Group;如果要实现一个消息只被一个消费者消费(“独占”)的效果,则将所有的 Consumer 放入一个 Consumer Group 中。
-
-注意:Consumer Group 中消费者的数量并不是越多越好,当其中消费者数量超过分区的数量时,会导致有消费者分配不到分区,从而造成消费者的浪费。
-
-Producer、Consumer 和 Consumer Group 之间的关系:
-
-
-
-
-
-### 9. Zookeeper
-
-存放 Kafka 集群相关元数据的组件。在 Zookeeper 集群中会
-
-- **保存 Topic 的状态信息**,例如分区的个数、分区的组成、分区的分布情况等;
-- **保存 Broker 的状态信息**;
-- **保存 Consumer 的消费信息**
-
-通过这些信息,Kafka 很好地将消息生产、消息存储、消息消费的过程结合起来。
-
-
-
-## Kafka 的拓扑结构
-
-### 典型的 Kafka 集群的拓扑结构
-
-
-
-
-
-
-
-Kafka 集群包含若干个 Producer,若干个 Broker (Kafka 集群支持水平扩展,一般 Broker 数量越多,整个 Kafka 集群的吞吐率也就越高),若干个 Consumer Group,以及一个 Zookeeper 集群。
-
-Kafka 通过 Zookeeper 管理集群配置。
-
-Producer 使用 Push 模式将消息发布到 Broker 上,Consumer 使用 Pull 模式从 Broker 上订阅并消费消息。
-
-
-
-### Kafka 数据流
-
-
-
-Producers 往 Brokers 中指定的 Topic Push 消息,Consumers 从 Brokers 里面 Pull 指定 Topic 的消息,然后进行业务处理。
-
-图中有两个 Topic:
-
-- Topic-0 有两个 Partition,Partition-0 和 Partition-1;
-- Topic-1 有一个 Partition。
-
-可以看到 Consumer-Group-1 中的 Consumer-2 没有分到 Partition 处理,这是有可能出现的。
\ No newline at end of file
diff --git "a/docs/Kafka/3_Kafka\347\232\204\351\253\230\345\217\257\347\224\250\345\216\237\347\220\206.md" "b/docs/Kafka/3_Kafka\347\232\204\351\253\230\345\217\257\347\224\250\345\216\237\347\220\206.md"
deleted file mode 100644
index 4b45a705..00000000
--- "a/docs/Kafka/3_Kafka\347\232\204\351\253\230\345\217\257\347\224\250\345\216\237\347\220\206.md"
+++ /dev/null
@@ -1,25 +0,0 @@
-# Kafka 的高可用原理
-
-Kafka 集群由若干个 Broker 组成,Topic 由若干个 Partition 组成,每个 Partition 可存在不同的 Broker 上。可以这样说,一个 Topic 的数据,分散在多个机器上,即每个机器上都存放一部分数据。
-
-## Kafka 0.8 以前
-
-Kafka 0.8 以前是没有高可用机制的。
-
-假设一个 Topic,由 3 个 Partiton 组成。3 个 Partition 在不同机器上,如果其中某一台机器宕掉了,则 Topic 的部分数据就丢失了。
-
-## Kafka 0.8 以后
-
-Kafka 0.8 以后,通过**副本机制**来实现高可用。
-
-每个 Partition 的数据都会同步到其他机器上,形成多个 Partition 副本。从每个 Partition 的副本集合中,选举出 Leader,其他的都是 Follower。Producer 和 Consumer 就和 Leader 打交道:
-
-- 写数据时,Leader 会将所用数据都同步到 Follower 上
-- 读数据时,直接读取 Leader 上的数据
-
-这样,若某个 Broker 宕机了,Broker 上的 Partition 在其他机器上是有副本的;若宕机的 Broker 上面有某个 Partition 的 Leader,则此时会从 Follower 中重新选择一个新的 Leader 出来。
-
-注意:Leader 的读写细节
-
-- 写数据时,Producer 向 Leader 写入,接着其他 Follower 主动从 Leader 中 pull 数据,当所有 Follower 同步好数据,就发送确认信息给 Leader,Leader 收到 Follower 的确认后,就返回写成功消息给 Producer。
-- 读数据时,只会从 Leader 中读取,但当只有一个消息已被所有 Follower 都同步成功,返回确认后时,这个消息才被消费者读到。
\ No newline at end of file
diff --git "a/docs/Kafka/4_Kafka\344\270\255\344\270\200\344\272\233\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/docs/Kafka/4_Kafka\344\270\255\344\270\200\344\272\233\345\270\270\350\247\201\351\227\256\351\242\230.md"
deleted file mode 100644
index e4ffec07..00000000
--- "a/docs/Kafka/4_Kafka\344\270\255\344\270\200\344\272\233\345\270\270\350\247\201\351\227\256\351\242\230.md"
+++ /dev/null
@@ -1,57 +0,0 @@
-# Kafka 中一些常见的问题
-
-## 消息丢失问题
-
-### 1. 消费端丢失数据
-
-默认情况下,Kafka 会自动提交 Offset,Kafka 认为 Consumer 已经处理消息了,但是 Consumer 可能在处理消息的过程中挂掉了。重启系统后,Consumer 会根据提交的 Offset 进行消费,也就丢失了一部分数据。
-
-解决:关闭自动提交 Offset,在处理完之后自己手动提交 Offset,就可以保证数据不会丢失。但可能会存在消息重复消费问题。
-
-
-
-### 2. Kafka 丢失数据
-
-比较常见的一个场景:Kafka 某个 Broker 宕机,然后重新选举新的 Leader ,但此时其他的 Follower 部分数据尚未同步,结果此时 Leader 挂了,然后选举某个 Follower 成 Leader ,丢失一部分数据。
-
-所以此时一般设置如下 4 个参数:
-
-- Topic 设置 `replication.factor` 参数
-
- 参数值必须大于 1,要去每个 Partition 必须有至少 2 个副本。
-
-- Kafka 服务端设置 `min.insync.replicas` 参数
-
- 参数值必须大于 1,要去每个 Partiton 必须有至少 2 个副本。
-
-- Producer 设置 `acks=all`
-
- 要求每条数据,必须是**写入所有副本,才认为写成功**。
-
-- Producer 端设置 `retries=MAX`
-
- MAX 即是一个超级大的数字,表示无限次重试。`retries=MAX`要求一旦写入数据失败,就无限重试。
-
-
-
-### 3. Producer 丢失数据
-
-如果 Producer 端设置了 `acks=all`,则不会丢失数据。
-
-Leader 在所有的 Follower 都同步到了消息之后,才认为本次写成功。如果没满足这个条件,生产者会进行无限次重试。
-
-
-
-## 消息重复消费问题
-
-### 原理
-
-Consumer 消费了数据后,每个一段时间,会将已消费过的消息的 Offset 进行提交,这样,重启后,可以继续从上次消费过的 Offset 来继续消费。测试时,直接 kill 进程,然后再重启后,会导致 Consumer 将有些消息处理了,但是还未来得及提交 Offset,重启后,少数消息会再消费一次。
-
-### 解决
-
-需要结合具体业务来思考,可从以下几个思路来考虑:
-
-- 如果要将数据写入数据库中,先根据主键查查询,如果这数据已存在,就不用插入数据了。
-- 向 Redis 中写入数据,可以使用 set,这样数据不会重复
-- 基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
\ No newline at end of file
diff --git "a/docs/Kafka/5_Kafka\347\211\271\347\202\271.md" "b/docs/Kafka/5_Kafka\347\211\271\347\202\271.md"
deleted file mode 100644
index 0db5f19f..00000000
--- "a/docs/Kafka/5_Kafka\347\211\271\347\202\271.md"
+++ /dev/null
@@ -1,78 +0,0 @@
-# Kafka 特点
-
-Kafka 特点:
-
-- 高可用
-- 持久性
-- 数据不易丢失
-- 高吞吐量
-
-## Kafka 高可用
-
-基于**副本机制**实现 Kafka 的高可用。
-
-
-
-## Kafka 持久性
-
-Kafka 集群接收到 Producer 发过来的消息后,将其持久化到磁盘。此外,还支持数据备份。
-
-
-
-## Kafka 数据不易丢失
-
-通过合理的配置,Kafka 消息不易丢失。
-
-
-
-## Kafka 高吞吐量
-
-Kafka 高吞吐量的原因:
-
-- 分区
-- 网路传输
-- 顺序读写
-- 零拷贝
-
-### 分区
-
-当生产者向对应 Topic 传递消息,消息通过**负载均衡机制**传递到不同的 Partition 以减轻单个服务器实例的压力;
-
-一个 Consumer Group 中可以有多个 Consumer,多个 Consumer 可以同时消费不同 Partition 的消息,大大的提高了消费者的并行消费能力。
-
-
-
-### 网络传输
-
-- 批量发送:在发送消息的时候,Kafka 不会直接将少量数据发送出去,否则每次发送少量的数据会增加网络传输频率,降低网络传输效率。Kafka 会先将消息缓存在内存中,当超过一个的大小或者超过一定的时间,那么会将这些消息进行批量发送。
-- 端到端压缩: Kfaka会将这些批量的数据进行压缩,将一批消息打包后进行压缩,发送给 Broker 服务器后,最终这些数据还是提供给消费者用,所以数据在服务器上还是保持压缩状态,不会进行解压,而且频繁的压缩和解压也会降低性能,最终还是以压缩的方式传递到消费者的手上,在 Consumer 端进行解压。
-
-
-
-### 顺序读写
-
-Kafka 是个可持久化的日志服务,它将数据以数据日志的形式进行追加,最后持久化在磁盘中。
-
-Kafka 消息存储时依赖于**文件系统**。为了利用数据的**局部相关性**:操作系统从磁盘中**以数据块为单位**读取数据,将一个数据块读入内存中,如果有相邻的数据,就不用再去磁盘中读取。所以,在某些情况下,**顺序磁盘访问能比随机内存访问还要快**。同时在写数据的时候也是将一整块数据块写入磁盘中,大大提升 I / O 效率。
-
-
-
-### 零拷贝
-
-普通的数据拷贝:
-
-
-
-零拷贝主要的任务是避免 CPU 做大量的数据拷贝任务,减少不必要的拷贝。
-
-**内存映射文件(Memory Mapped Files,mmap)**在 64 位操作系统中一般可以表示 20G 的数据文件,它的工作原理是直接利用操作系统的页缓存来实现文件到物理内存的直接映射。
-
-
-
-使用 mmap 替代 read 很明显减少了 1 次拷贝,当拷贝数据量很大时,无疑提升了效率。
-
-
-
-# 参考资料
-
-- [解密Kafka吞吐量高的原因](https://news.cndns.com/ArticlesDetail/articlesdel/id/9685)
\ No newline at end of file
diff --git "a/docs/MySQL/1_\351\224\201\346\234\272\345\210\266.md" "b/docs/MySQL/1_\351\224\201\346\234\272\345\210\266.md"
index 308ff73b..d3998817 100644
--- "a/docs/MySQL/1_\351\224\201\346\234\272\345\210\266.md"
+++ "b/docs/MySQL/1_\351\224\201\346\234\272\345\210\266.md"
@@ -50,7 +50,7 @@
- 会出现死锁
- 锁定粒度介于表锁和行锁之间,并发度一般
-
+
diff --git "a/docs/MySQL/2_\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253\345\256\236\347\216\260.md" "b/docs/MySQL/2_\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253\345\256\236\347\216\260.md"
index 1d34445c..575e0580 100644
--- "a/docs/MySQL/2_\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253\345\256\236\347\216\260.md"
+++ "b/docs/MySQL/2_\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253\345\256\236\347\216\260.md"
@@ -32,7 +32,7 @@ UPDATE name SET name="c" WHERE id=1;
因为没有使用 `START TRANSACTION` 将上面的操作当成一个事务来执行,根据 MySQL 的 AUTOCOMMIT 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。
-
+
undo log 主要有两个作用:
@@ -79,7 +79,7 @@ InnoDB 存储引擎在开启一个新事务后,执行每个 select 语句前
事务可见性示意图:
-
+
### 快照读 & 当前读
@@ -159,7 +159,7 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
如下图所示:
-
+
### 几个问题
@@ -179,7 +179,7 @@ delete from tb where id = 9
根据 id=9 条件定位,此时给 id = 9 的索引加上记录锁,根据 name 值(name是主键)到主索引中检索获得记录,再给该记录加上记录锁。
-
+
> 问题二:间隙锁是否用在非唯一索引的当前读中?
@@ -191,7 +191,7 @@ delete from tb1 where id = 9
-- key 是非唯一索引
```
-
+
可以看出,在 (6,9]、(9,11] 加了间隙锁。
@@ -205,7 +205,7 @@ delete from tb2 where id = 9
-- 没有为 id 建立索引
```
-
+
此时对所有的间隙都上锁(功能上相当于锁表)。
diff --git "a/docs/MySQL/3_\347\264\242\345\274\225.md" "b/docs/MySQL/3_\347\264\242\345\274\225.md"
index d8b1fa1d..b08a6ddf 100644
--- "a/docs/MySQL/3_\347\264\242\345\274\225.md"
+++ "b/docs/MySQL/3_\347\264\242\345\274\225.md"
@@ -55,7 +55,7 @@
c)非叶子节点的指针:P[1],P[2],... ,P[M];其中 P[1] 指向关键字小于 K[1] 的子树,P[M] 指向关键字大于 K[M-1] 的子树,其他 P[i] 关键字属于(K[i-1],K[i]) 的子树
- 
+ 
### B+ 树
@@ -69,7 +69,7 @@ B+ 树是 B 树的变体,其定义基本与 B 树相同,除了:
- 所有叶子节点均有一个链指针指向下一个叶子节点
- 
+ 
数据库系统普遍采用 B+ 树作为索引结构,主要有以下原因:
@@ -110,7 +110,7 @@ MySQL 索引使用的是 B 树中的 B+ 树,但索引是在存储引擎层实
MyISAM 引擎使用 B+ 树作索引结构,**叶子节点的 data 域存放的是数据记录的地址**,所有索引均是非聚集索引。
-
+
上图是一个 MyISAM 表的主索引(Primary key)示意图。
@@ -118,7 +118,7 @@ MyISAM 引擎使用 B+ 树作索引结构,**叶子节点的 data 域存放的
在 MyISAM 中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求 key 是唯一的,而辅助索引的 **key 可以重复**。如果在 Col2 上建立一个辅助索引,则该辅助索引的结构如下:
-
+
同样也是一棵 B+ 树,data 域保存数据记录的地址。
@@ -130,7 +130,7 @@ InnoDB 也使用 B+ 树作为索引结构。有且仅有一个聚集索引,和
InnoDB 的数据文件本身就是索引文件。MyISAM 索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在 InnoDB 中,表数据文件本身就是按 B+ 树组织的一个索引结构,这棵树的**叶子节点 data 域保存了完整的数据记录**。这个索引的 key 是数据表的主键,因此 **InnoDB 表数据文件本身就是主索引**。
-
+
上图是 InnoDB 主索引(同时也是数据文件)的示意图。可以看到叶子节点包含了完整的数据记录。
@@ -142,7 +142,7 @@ InnoDB 的数据文件本身就是索引文件。MyISAM 索引文件和数据文
与 MyISAM 索引的不同是 **InnoDB 的辅助索引 data 域存储相应记录主键的值**而不是地址。例如,定义在 Col3 上的一个辅助索引:
-
+
diff --git "a/docs/MySQL/4_MySQL\346\236\266\346\236\204.md" "b/docs/MySQL/4_MySQL\346\236\266\346\236\204.md"
index eea5bfd0..38a768d9 100644
--- "a/docs/MySQL/4_MySQL\346\236\266\346\236\204.md"
+++ "b/docs/MySQL/4_MySQL\346\236\266\346\236\204.md"
@@ -12,7 +12,7 @@
主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。**现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了**。
-
+
diff --git "a/docs/MySQL/5_MySQL\344\274\230\345\214\226.md" "b/docs/MySQL/5_MySQL\344\274\230\345\214\226.md"
index 339723de..d470d532 100644
--- "a/docs/MySQL/5_MySQL\344\274\230\345\214\226.md"
+++ "b/docs/MySQL/5_MySQL\344\274\230\345\214\226.md"
@@ -1,4 +1,4 @@
-# MySQL 优化
+# MySQL 调优
## SQL 语句优化
diff --git "a/docs/MySQL/6_MySQL\346\225\260\346\215\256\347\261\273\345\236\213.md" "b/docs/MySQL/6_MySQL\346\225\260\346\215\256\347\261\273\345\236\213.md"
index e55b7d56..b15db3d3 100644
--- "a/docs/MySQL/6_MySQL\346\225\260\346\215\256\347\261\273\345\236\213.md"
+++ "b/docs/MySQL/6_MySQL\346\225\260\346\215\256\347\261\273\345\236\213.md"
@@ -24,7 +24,7 @@ VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内
MySQL 提供了两种相似的日期时间类型:DATETIME 和 TIMESTAMP。
-### 1. DATETIME
+### DATETIME
能够保存从 1000 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。
@@ -32,7 +32,7 @@ MySQL 提供了两种相似的日期时间类型:DATETIME 和 TIMESTAMP。
默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATETIME 值,例如“2008-01-16 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。
-### 2. TIMESTAMP
+### TIMESTAMP
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年到 2038 年。
@@ -50,7 +50,24 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
- TIMESTAMP 只需要使用 4 个字节的存储空间,但 DATETIME 需要耗费 8 个字节的存储空间。但是,这样同样造成了一个问题,TIMESTAMP 表示的时间范围更小。
- - DATETIME :1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
- - TIMESTAMP: 1970-01-01 00:00:01 ~ 2037-12-31 23:59:59
+ - DATETIME :`1000-01-01 00:00:00 ~ 9999-12-31 23:59:59`
+ - TIMESTAMP: `1970-01-01 00:00:00 ~ 2037-12-31 23:59:59`
注意 5.6.4 之后的 MySQL 多出了一个需要 0 ~ 3 字节的小数位。DATETIME 和 TIMESTAMP 会有几种不同的存储空间占用。
+
+### 数值型时间戳
+
+时间戳的定义是从一个基准时间 [ 1970-1-1 00:00:00 +0:00 ] 开始算起,用整数表示,以秒计时,随着时间的流逝这个时间整数不断增加。
+
+一个数值,就可以完美地表示时间了,而且这个数值是一个绝对数值,即无论的身处地球的任何角落,这个表示时间的时间戳,都是一样的,生成的数值都是一样的,并且没有时区的概念。
+
+MySQL 中可以使用 int 或者 bigint 类型的时间戳来表示时间。
+
+小结:
+
+| 日期类型 | 存储空间 | 日期格式 | 日期范围 | 时区问题 |
+| --------- | -------- | ------------------- | ---------------------------------------------- | -------- |
+| DATETIME | 8 字节 | yyyy-MM-dd HH:mm:ss | 1000-01-01 00:00:00 ~
9999-12-31 23:59:59 | 存在 |
+| TIMESTAMP | 4 字节 | yyyy-MM-dd HH:mm:ss | 1970-01-01 00:00:00 ~
2037-12-31 23:59:59 | 不存在 |
+| 时间戳 | 4 字节 | 数值 | 1970-01-01 00:00:00 之后的时间 | 不存在 |
+
diff --git "a/docs/MySQL/RabbitMQ/1_\344\270\273\346\265\201\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266.md" "b/docs/MySQL/RabbitMQ/1_\344\270\273\346\265\201\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266.md"
deleted file mode 100644
index c80a19fb..00000000
--- "a/docs/MySQL/RabbitMQ/1_\344\270\273\346\265\201\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266.md"
+++ /dev/null
@@ -1,34 +0,0 @@
-# 主流消息中间件
-
-## 1. ActiveMQ
-
-ActiveMQ 是 Apache 出品的最流行的、性能强劲的开源消息总线,并且其是一个完全支持 JMS 规范的消息中间件。其丰富的 API、多种集群构建模式使得它成为业界老牌消息中间件,广泛应用于中小型企业。
-
-## 2. Kafka
-
-Kafka 是 LinkedIn 开源的分布式发布-订阅消息系统,目前属于 Apache 顶级项目。
-
-Kafka 主要特点是基于 Pull 的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8 版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。
-
-## 3. RocketMQ
-
-RocketMQ 是阿里开源的消息中间件,目前也已经孵化为 Apache 顶级项目,它是纯 Java 开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。
-
-RocketMQ 思路起源于 Kafka,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog 分发等场景。
-
-## 4. RabbitMQ
-
-RabbitMQ 是使用 Erlang 语言开发的开源消息队列系统,基于 AMQP 协议来实现。
-
-AMQP 的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP 协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
-
-## 对比
-
-| 消息中间件 | ActiveMQ | Kafka | RocketMQ | RabbitMQ |
-| :----------------------: | :-----------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :------------------------------------------------: |
-| *吞吐量* | 万数量级 | 十万级,高吞吐 | 十万级,高吞吐 | 万数量级 |
-| *Topic 数量对吞吐量影响* | / | Topic 从几十到几百时,吞吐量会大幅度下降,
在同等机器下,Kafka 尽量保证 Topic 数量不要过多,如果要支撑大规模的 Topic,需要增加更多的机器 | Topic 可达百/千级,吞吐量下降幅度小
在同等机器下,可以支撑大量的 Topic | / |
-| *时效性* | 毫秒级 | 毫秒级 | 毫秒级 | 微秒级 |
-| *可用性* | 高
基于主从架构实现高可用高 | 非常高
分布式架构 | 非常高
分布式架构 | 高
基于主从架构实现高可用高 |
-| *可靠性* | 丢失数据的概率低 | 优化参数配置,
可以做到零丢失 | 优化参数配置,
可以做到零丢失 | 基本不会丢失数据 |
-| *功能* | 功能较完备 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | MQ 功能较为完善,还是分布式的,扩展性好 | 基于 ErLang 开发,并发能力很强,性能极好,延时很低 |
\ No newline at end of file
diff --git "a/docs/MySQL/RabbitMQ/2_RabbitMQ\346\225\264\345\220\210SpringBoot.md" "b/docs/MySQL/RabbitMQ/2_RabbitMQ\346\225\264\345\220\210SpringBoot.md"
deleted file mode 100644
index e69de29b..00000000
diff --git "a/docs/MySQL/RabbitMQ/2_RabbitMQ\346\246\202\350\277\260.md" "b/docs/MySQL/RabbitMQ/2_RabbitMQ\346\246\202\350\277\260.md"
deleted file mode 100644
index 4945be10..00000000
--- "a/docs/MySQL/RabbitMQ/2_RabbitMQ\346\246\202\350\277\260.md"
+++ /dev/null
@@ -1,76 +0,0 @@
-# RabbitMQ 概述
-
-## 简介
-
-RabbitMQ 是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用中间共享数据,RabbitMQ 是使用 **Erlang 语言**来编写的,并且 RabbitMQ 是基于 AMQP 协议的。
-
-特点:
-
-- 开源、性能优秀,稳定性好
-- 提供可靠性消息投递模式(confirm)、返回模式(return)
-- 与 SpringAOP 完美的整合、API 丰富
-- 集群模式丰富,表达式配置,HA 模式,镜像队列模型
-- 保证数据不丢失的前提做到高可靠性、可用性
-
-RabbitMQ 高性能的原因:
-
-**Erlang 语言**最初用在交换机的架构模式,这样使得 RabbitMQ 在 Broker 之间进行数据交互的性能时非常优秀的。Erlang 的优点:Erlang 有着和原生 Socket 一样的延迟。
-
-## AMQP 协议
-
-AMQP(Advanced Message Queuing Protocol)协议,即高级消息队列协议。
-
-AMQP 是具有现在特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
-
-### 协议模型
-
-
-
-### 核心概念
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git "a/docs/MySQL/RabbitMQ/3_\346\266\210\346\201\257\345\217\257\351\235\240\346\200\247\346\212\225\351\200\222\347\232\204\350\247\243\345\206\263\346\226\271\346\241\210\345\256\236\347\216\260.md" "b/docs/MySQL/RabbitMQ/3_\346\266\210\346\201\257\345\217\257\351\235\240\346\200\247\346\212\225\351\200\222\347\232\204\350\247\243\345\206\263\346\226\271\346\241\210\345\256\236\347\216\260.md"
deleted file mode 100644
index e69de29b..00000000
diff --git "a/docs/MySQL/\345\210\207\345\210\206\345\244\215\345\210\266\351\227\256\351\242\230.md" "b/docs/MySQL/\345\210\207\345\210\206\345\244\215\345\210\266\351\227\256\351\242\230.md"
new file mode 100644
index 00000000..b8567dd9
--- /dev/null
+++ "b/docs/MySQL/\345\210\207\345\210\206\345\244\215\345\210\266\351\227\256\351\242\230.md"
@@ -0,0 +1,8 @@
+# 一、切分
+
+
+
+
+
+# 二、复制
+
diff --git "a/docs/OO/2_\345\210\233\345\273\272\345\236\213.md" "b/docs/OO/2_\345\210\233\345\273\272\345\236\213.md"
index 35a293b4..27f2ac57 100644
--- "a/docs/OO/2_\345\210\233\345\273\272\345\236\213.md"
+++ "b/docs/OO/2_\345\210\233\345\273\272\345\236\213.md"
@@ -12,7 +12,7 @@
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
-
+
### Implementation
@@ -279,7 +279,7 @@ public class Singleton {
客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
-
+
### Implementation
@@ -376,7 +376,7 @@ public class Client {
下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。
-
+
### Implementation
@@ -440,7 +440,7 @@ public class ConcreteFactory2 extends Factory {
从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。
-
+
### Implementation
@@ -536,7 +536,7 @@ public class Client {
当然,光有指导者是不够的,必须要有能具体实现每步的对象,在生成器模式中称这些实现对象为**生成器**。
这样一来,**指导者就是可以重用的构建过程,而生成器是可以被切换的具体实现**。
-
+
### Implementation1
@@ -746,7 +746,7 @@ public class Client {
生成器的调用顺序:
-
+
### Implementation2
@@ -836,7 +836,7 @@ abcdefghijklmnopqrstuvwxyz
### Class Diagram
-
+
### Implementation
diff --git "a/docs/OO/3_\350\241\214\344\270\272\345\236\213.md" "b/docs/OO/3_\350\241\214\344\270\272\345\236\213.md"
index 9b5f8286..e91dd3cc 100644
--- "a/docs/OO/3_\350\241\214\344\270\272\345\236\213.md"
+++ "b/docs/OO/3_\350\241\214\344\270\272\345\236\213.md"
@@ -11,7 +11,7 @@
- Handler:定义处理请求的接口,并且实现后继链(successor)
-
+
### Implementation
@@ -158,7 +158,7 @@ request-2 is handle by ConcreteHandler2
- Invoker:通过它来调用命令
- Client:可以设置命令与命令的接收者
-
+
### Implementation1
```java
@@ -232,7 +232,7 @@ public class Client {
设计一个遥控器,可以控制电灯开关。
-
+
```java
public interface Command {
@@ -349,7 +349,7 @@ public class Client {
- TerminalExpression:终结符表达式,每个终结符都需要一个 TerminalExpression。
- Context:上下文,包含解释器之外的一些全局信息。
-
+
### Implementation
@@ -482,7 +482,7 @@ false
- Iterator 主要定义了 hasNext() 和 next() 方法。
- Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator。
-
+
### Implementation
@@ -571,17 +571,17 @@ public class Client {
- Mediator:中介者,定义一个接口用于与各同事(Colleague)对象通信。
- Colleague:同事,相关对象
-
+
### Implementation
Alarm(闹钟)、CoffeePot(咖啡壶)、Calendar(日历)、Sprinkler(喷头)是一组相关的对象,在某个对象的事件产生时需要去操作其它对象,形成了下面这种依赖结构:
-
+
使用中介者模式可以将复杂的依赖结构变成星形结构:
-
+
```java
public abstract class Colleague {
@@ -744,7 +744,7 @@ doSprinkler()
备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;
一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。
-
+
### Implementation
@@ -916,7 +916,7 @@ public class Client {
主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。
-
+
### Class Diagram
@@ -924,13 +924,13 @@ public class Client {
观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。
-
+
### Implementation
天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。
-
+
```java
public interface Subject {
@@ -1059,7 +1059,7 @@ State:状态接口,用来封装与上下文的**一个特定状态所对应
ConcreteState:具体实现状态处理的类,每个类实现一个跟上下文相关的状态的具体处理。
-
+
### Implementation1
实现在线投票:
@@ -1080,7 +1080,7 @@ ConcreteState:具体实现状态处理的类,每个类实现一个跟上下
程序结构如下图:
-
+
```java
/**
@@ -1213,7 +1213,7 @@ public class Client {
糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。
-
+
```java
public interface State {
@@ -1514,7 +1514,7 @@ No gumball dispensed
- Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。
- Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。
-
+
### 与状态模式的比较
@@ -1535,7 +1535,7 @@ No gumball dispensed
2. 对老客户报的价格,根据客户年限,给予一定的折扣
3. 对大客户报的价格,根据大客户的累计消费金额,给予一定的折扣
-
+
```java
/**
@@ -1702,13 +1702,13 @@ squeak!
### Class Diagram
-
+
### Implementation
冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。
-
+
```java
public abstract class CaffeineBeverage {
@@ -1805,7 +1805,7 @@ Tea.addCondiments
- ConcreteVisitor:具体访问者,存储遍历过程中的累计结果
- ObjectStructure:对象结构,可以是组合结构,或者是一个集合。
-
+
### Implementation
@@ -2010,7 +2010,7 @@ Number of items: 6
### Class Diagram
-
+
### Implementation
diff --git "a/docs/OO/4_\347\273\223\346\236\204\345\236\213.md" "b/docs/OO/4_\347\273\223\346\236\204\345\236\213.md"
index 4839eb59..f3144f52 100644
--- "a/docs/OO/4_\347\273\223\346\236\204\345\236\213.md"
+++ "b/docs/OO/4_\347\273\223\346\236\204\345\236\213.md"
@@ -6,7 +6,7 @@
把一个类接口转换成另一个用户需要的接口。
-
+
### Class Diagram
Target:定义客户端需要的跟特定领域相关的接口。
@@ -16,7 +16,7 @@ Adaptee:已经存在的接口,通常能满足客户端的功能要求,
Adapter:适配器,把Adaptee适配成为Client需要的Target。
-
+
### Implementation1
美国的电饭煲是在电压为 110V 下工作,而中国的电饭煲在电压 220V 下工作。要求将在美国使用的电饭煲适配成能在中国使用。
@@ -182,7 +182,7 @@ public class Client {
- Abstraction:定义抽象类的接口
- Implementor:定义实现类接口
-
+
### Implementation
@@ -343,7 +343,7 @@ Composite:组合对象,通常会存储子组件,定义包含子组件的
组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。
-
+
### Implementation1
商品类别树的管理,比如有如下所示的商品类别树:
@@ -640,7 +640,7 @@ public class Client {
装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
-
+
### Implementation1
给普通手机装饰上彩铃等功能。
@@ -778,7 +778,7 @@ IPhone打电话
下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。
-
+
```java
public interface Beverage {
@@ -883,7 +883,7 @@ public class Client {
### Class Diagram
-
+
### Implementation
@@ -942,7 +942,7 @@ public class Client {
- IntrinsicState:内部状态,享元对象共享内部状态
- ExtrinsicState:外部状态,每个享元对象的外部状态不同
-
+
### Implementation
@@ -1042,7 +1042,7 @@ Java 利用缓存来加速大量小对象的访问时间。
- RealSubject:具体的目标对象,真正实现目标接口要求的功能。
-
+
### Implementation
diff --git "a/docs/OO/6_\345\205\263\347\263\273\347\261\273\345\233\276.md" "b/docs/OO/6_\345\205\263\347\263\273\347\261\273\345\233\276.md"
index f7c1fa10..a0532acf 100644
--- "a/docs/OO/6_\345\205\263\347\263\273\347\261\273\345\233\276.md"
+++ "b/docs/OO/6_\345\205\263\347\263\273\347\261\273\345\233\276.md"
@@ -6,7 +6,7 @@
用来描述继承关系,在 Java 中使用 extends 关键字。
-
+
```text
@startuml
@@ -27,7 +27,7 @@ Vihical <|-- Trunck
用来实现一个接口,在 Java 中使用 implements 关键字。
-
+
```text
@startuml
@@ -48,7 +48,7 @@ MoveBehavior <|.. Run
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。
-
+
```text
@startuml
@@ -71,7 +71,7 @@ Computer o-- Screen
和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。
-
+
```text
@startuml
@@ -92,7 +92,7 @@ Company *-- DepartmentB
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。
-
+
```text
@startuml
@@ -115,7 +115,7 @@ School "1" - "n" Student
- A 类是 B 类方法当中的一个参数;
- A 类向 B 类发送消息,从而影响 B 类发生变化。
-
+
```text
@startuml
diff --git a/docs/README.md b/docs/README.md
index 8a522bbe..03951a42 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -28,6 +28,7 @@
- [JVM 调优](JVM/4_JVM调优.md)
- [类文件结构](JVM/5_类文件结构.md)
- [类加载机制](JVM/6_类加载机制.md)
+- [Java 程序编译和运行过程](JVM/7_Java程序编译和运行过程.md)
## Java 并发
@@ -68,9 +69,9 @@
# 📝 编程题
-## 数据结构相关
+## 数据结构系列
-- [数组和矩阵](AimForOffer/数据结构相关/1_数组和矩阵.md)
+- [数组 & 矩阵](AimForOffer/数据结构相关/1_数组和矩阵.md)
- [字符串](AimForOffer/数据结构相关/2_字符串.md)
- [链表](AimForOffer/数据结构相关/3_链表.md)
- [树](AimForOffer/数据结构相关/4_树.md)
@@ -79,7 +80,7 @@
- [堆](AimForOffer/数据结构相关/7_堆.md)
- [ 哈希.](AimForOffer/数据结构相关/8_哈希.md)
-## 算法相关
+## 算法思维系列
- [查找](AimForOffer/算法思想相关/1_查找.md)
- [排序](AimForOffer/算法思想相关/2_排序.md)
@@ -110,6 +111,7 @@
- [MySQL架构](MySQL/4_MySQL架构.md)
- [MySQL优化](MySQL/5_MySQL优化.md)
- [MySQL数据类型](MySQL/6_MySQL数据类型.md)
+- [切分 & 复制问题](MySQL/切分复制问题.md)
- [补充知识](MySQL/6_补充知识.md)
## Redis
@@ -121,64 +123,101 @@
- [Redis 持久化机制](Redis/5_持久化机制.md)
- [Redis 事务](Redis/6_事务.md)
- [缓存问题](Redis/7_缓存问题.md)
+- [Redis 部署方式](Redis/8_部署方式.md)
+- [Redis 实战](Redis/9_实战.md)
-# ☎️ 常用框架
+# 🎓 系统设计
-## Spring
+- [系统设计基础](1_基础.md)
-## SpringCloud
+## 安全性
-## Zookeeper
+- [Cookie & Session &Token](Safety/Cookie_Session_Token.md)
+- [常见攻击技术及防御](Safety/常见攻击技术及防御.md)
-## Kafka
+## 分布式
-## ElasticSearch
+- [分布式系统基本概念](distribution/1_分布式系统设计理念.md)
+- [CAP 理论 & BASE 理论](distribution/2_CAP理论.md)
+- [分布式锁](distribution/3_分布式锁.md)
+- [分布式事务](distribution/4_分布式事务.md)
+- [Paxos 算法 & Raft 算法](distribution/5_Paxos算法.md)
-# 📖 系统设计
+## 集群
-## Web 安全
+- [负载均衡](cluster/1_负载均衡.md)
+- [集群下的 Session 管理](cluster/2_集群下的Session管理.md)
-- [常见安全问题](Safety/1_常见安全问题.md)
-- [跨站脚本攻击](Safety/2_跨站脚本攻击.md)
-- [跨站请求伪造](Safety/3_跨站请求伪造.md)
-- [Cookies问题](Safety/4_Cookies问题.md)
-- [点击劫持问题](Safety/5_点击劫持问题.md)
-- [传输安全](Safety/6_传输安全.md)
-- [密码安全](Safety/7_密码安全.md)
-- [接入层注入问题](Safety/8_接入层注入问题.md)
-- [接入层上传问题](Safety/9_接入层上传问题.md)
-- [信息泄露](Safety/10_信息泄露.md)
-- [DoS攻击](Safety/11_DoS攻击.md)
-- [重放攻击](Safety/12_重放攻击.md)
+## 缓存
-## 分布式
+- [缓存需要考虑的问题](cache/1_缓存需要考虑的问题.md)
+- [缓存常见问题](cache/2_缓存常见问题.md)
+- [数据分布](cache/3_数据分布.md)
-- [分布式系统设计理念](distribution/1_分布式系统设计理念.md)
-- [CAP理论](distribution/2_CAP理论.md)
-- [BASE理论](distribution/3_BASE理论.md)
-- [分布式锁](distribution/4_分布式锁.md)
-- [分布式事务](distribution/5_分布式事务.md)
-- [分布式缓存的一致性哈希算法](distribution/6_分布式缓存的一致性哈希算法.md)
+## 消息队列
-## 网站架构
+- [消息队列](1_消息队列.md)
+- [Kafka 原理及应用](Kafka&RabbitMQ/Kafka.md)
+- [RabbitMQ 原理及应用](Kafka&RabbitMQ/RabbitMQ.md)
+- [Kafka & RabbitMQ 比较](Kafka&RabbitMQ/Kafka_RabbitMQ.md)
-- [网站架构](web_architecture/1_网站架构.md)
-- [设计秒杀系统](web_architecture/2_设计秒杀系统.md)
+# ☎️ 常用框架
-# 💪 工具
+## Spring
+
+- [Spring 概述](Spring/1_Spring概述.md)
+- [Spring IoC 原理](Spring/2_SpringIoC原理.md)
+- [Spring AOP 原理](Spring/3_SpringAOP原理.md)
+- [Spring MVC 原理](Spring/4_SpringMVC原理.md)
+- [Spring 事务管理](Spring/5_Spring事务管理.md)
+- [Spring 中用到的设计模式](Spring/6_Spring中用到的设计模式.md)
+- [MyBatis](Spring/7_MyBatis.md)
+
+## SpringBoot
+
+- [SpringBoot 概述](SpringBoot/1_SpringBoot概述.md)
+- [配置文件](SpringBoot/2_配置文件.md)
+- [常用注解](SpringBoot/3_常用注解.md)
+- [请求参数校验](SpringBoot/4_请求参数校验.md)
+- [全局异常处理](SpringBoot/5_全局异常处理.md)
+- [实现定时任务](SpringBoot/6_实现定时任务.md)
+
+## Zookeeper
+
+- [Zookeeper 概述](Zookeeper/1_概述.md)
+- [Zookeeper 数据模型](Zookeeper/2_数据模型.md)
+- [Zookeeper 的原理](Zookeeper/3_原理.md)
+- [Zookeeper 的应用](Zookeeper/4_应用.md)
+
+# 📖 工具
## Git
- [git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
- [git - 图解](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
+- [github - 小技巧](https://snailclimb.gitee.io/javaguide/#/docs/tools/Github%E6%8A%80%E5%B7%A7)
-## Github
+## Docker
-- [github - 小技巧](https://snailclimb.gitee.io/javaguide/#/docs/tools/Github%E6%8A%80%E5%B7%A7)
+- [Docker 概述](https://snailclimb.gitee.io/javaguide/#/docs/tools/Docker)
+- [Docker 实战](https://snailclimb.gitee.io/javaguide/#/docs/tools/Docker%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98)
# 📚 参考资料
-- [小专栏](reference/小专栏.md)
-- [参考仓库](reference/参考仓库.md)
-- [参考书籍](reference/参考书籍.md)
-- [慕课网](reference/慕课网.md)
+## 参考仓库
+
+- [Java 基础知识](reference/Java基础_1.md)
+- [Java 进阶知识](reference/Java进阶_1.md)
+- [编程题](reference/编程题_1.md)
+
+## 参考书籍
+
+- [Java 基础](reference/Java基础_2.md)
+- [编程题](reference/编程题_2.md)
+- [数据库](reference/数据库.md)
+- [常用框架](reference/常用框架.md)
+
+## 小专栏
+
+- [后端面试进阶指南](https://xiaozhuanlan.com/CyC2018)
+- [Java 面试进阶指南](https://xiaozhuanlan.com/javainterview)
\ No newline at end of file
diff --git a/docs/README_1.md b/docs/README_1.md
deleted file mode 100644
index d6b5618f..00000000
--- a/docs/README_1.md
+++ /dev/null
@@ -1,324 +0,0 @@
-# ✏️ 计算机基础
-
-🌐 `:globe_with_meridians:`
-
-| 🚀 `:rocket:` | ✈️ `:airplane:` | 🚁 `:helicopter:` |
-| ------------ | -------------- | ---------------- |
-| | | |
-
-
-
-## 🌐 计算机网络
-
-- [概述](Net/1_概述.md)
-- [物理层](Net/2_物理层.md)
-- [数据链路层](Net/3_数据链路层.md)
-- [网络层](Net/4_网络层.md)
-- [运输层](Net/5_运输层.md)
-- [应用层](Net/6_应用层.md)
-
-## 🌐 操作系统
-
-- [第一节 操作系统概述](OS/1_操作系统概述.md)
-- [第二节 进程管理](OS/2_进程管理.md)
-- [第三节 死锁](OS/3_死锁.md)
-- [第四节 内存管理](OS/4_内存管理.md)
-- [第五节 设备管理](OS/4_设备管理.md)
-- [第六节 链接](OS/6_链接.md)
-
-## 🌐 数据结构和算法
-
-- [第一节 概述](data_structure/1_概述.md)
-- [第二节 线性表](data_structure/2_线性表.md)
-- [第三节 栈和队列](data_structure/3_栈和队列.md)
-- [第四节 树](data_structure/4_树.md)
-- [第五节 图](data_structure/5_图.md)
-- [第六节 集合和映射](data_structure/6_集合和映射.md)
-- [第七节 并查集](data_structure/7_并查集.md)
-- [第八节 优先队列和堆](data_structure/8_优先队列和堆.md)
-- [第九节 哈希表](data_structure/9_哈希表.md)
-- [第十节 排序](data_structure/10_排序.md)
-- [第十一节 线段树](data_structure/11_线段树.md)
-- [第十二节 Trie树](data_structure/12_Trie树.md)
-- [第十三节 AVL](data_structure/13_AVL.md)
-- [第十四节 红黑树](data_structure/14_红黑树.md)
-
-## 🌐 HTTP
-
-- [第一节 HTTP概述](HTTP/1_HTTP概述.md)
-- [第二节 HTTP状态码](HTTP/2_HTTP状态码.md)
-- [第三节 具体应用](HTTP/3_具体应用.md)
-- [第四节 HTTPS](HTTP/4_HTTPS.md)
-- [第五节 get和post比较](HTTP/5_get和post比较.md)
-
-## 🌐 Linux
-
-- [第一节 Linux概论](Linux/1_Linux概论.md)
-- [第二节 Linux文件系统](Linux/2_Linux文件系统.md)
-- [第三节 Linux常用命令](Linux/3_Linux常用命令.md)
-- [第四节 Liunx进程管理](Linux/4_Liunx进程管理.md)
-- [第五节 Linux压缩与打包](Linux/5_Linux压缩与打包.md)
-
-# ☕️ Java
-
-## 🌈 Java 基础
-
-- [第一节 数据类型](JavaBasics/1_数据类型.md)
-- [第二节 String](JavaBasics/2_String.md)
-- [第三节 运算](JavaBasics/3_运算.md)
-- [第四节 Object通用方法](JavaBasics/4_Object通用方法.md)
-- [第五节 关键字](JavaBasics/5_关键字.md)
-- [第六节 反射](JavaBasics/6_反射.md)
-- [第七节 异常](JavaBasics/7_异常.md)
-- [第八节 泛型](JavaBasics/8_泛型.md)
-- [第九节 注解](JavaBasics/9_注解.md)
-- [第十节 Java常见对象](JavaBasics/10_Java常见对象.md)
-- [第十一节 抽象类和接口](JavaBasics/11_抽象类和接口.md)
-- [第十二节 其他](JavaBasics/12_其他.md)
-
-## 🌈 Java 容器
-
-- [第一节 Java容器概览](JavaContainer/1_Java容器概览.md)
-- [第二节 容器中的设计模式](JavaContainer/2_容器中的设计模式.md)
-- [第三节 容器源码分析 - List](JavaContainer/3_容器源码分析%20-%20List.md)
-- [第四节 容器源码分析 - Map](JavaContainer/4_容器源码分析%20-%20Map.md)
-- [第五节 容器源码分析 - 并发容器](JavaContainer/5_容器源码分析%20-%20并发容器.md)
-
-## 🌈 Java 虚拟机
-
-- [第一节 运行时数据区域](JVM/1_JVM.md)
-- [第二节 HotSpot 虚拟机对象](JVM/2_JVM.md)
-- [第三节 String 类和常量池](JVM/3_JVM.md)
-- [第四节 8 种基本类型的包装类和常量池](JVM/4_JVM.md)
-- [第五节 垃圾收集](JVM/5_JVM.md)
-- [第六节 内存分配与回收策略](JVM/6_JVM.md)
-- [第七节 类加载机制](JVM/7_JVM.md)
-
-## 🌈 Java 并发
-
-- [第一节 基础知识](Java_Concurrency/1_基础知识.md)
-- [第二节 并发理论](Java_Concurrency/2_并发理论.md)
-- [第三节 并发关键字](Java_Concurrency/3_并发关键字.md)
-- [第四节 Lock 体系](Java_Concurrency/4_Lock%20体系.md)
-- [第五节 原子操作类](Java_Concurrency/5_原子操作类.md)
-- [第六节 并发容器](Java_Concurrency/6_并发容器.md)
-- [第七节 并发工具](Java_Concurrency/7_并发工具.md)
-- [第八节 线程池](Java_Concurrency/8_线程池.md)
-- [第九节 并发实践](Java_Concurrency/9_并发实践.md)
-
-## 🌈 JavaIO
-
-- [第一节 概览](JavaIO/1_概览.md)
-- [第二节 磁盘操作](JavaIO/2_磁盘操作.md)
-- [第三节 字节操作](JavaIO/3_字节操作.md)
-- [第四节 字符操作](JavaIO/4_字符操作.md)
-- [第五节 对象操作](JavaIO/5_对象操作.md)
-- [第六节 网络操作](JavaIO/6_网络操作.md)
-- [第七节 NIO](JavaIO/7_NIO.md)
-- [第八节 JavaIO方式](JavaIO/8_JavaIO方式.md)
-
-## 🌈 正则表达式
-
-- [第一节 概述](Regex/1_概述.md)
-- [第二节 应用](Regex/2_应用.md)
-
-# 👫 面向对象
-
-## 🚩 设计模式
-
-- [第一节 概述](OO/1_概述.md)
-- [第二节 创建型](OO/2_创建型.md)
-- [第三节 行为型](OO/3_行为型.md)
-- [第四节 结构型](OO/4_结构型.md)
-
-## 🚩 面向对象思想
-
-- [第一节 面向对象三大特性](OO/5_面向对象三大特性.md)
-- [第二节 关系类图](OO/6_关系类图.md)
-- [第三节 面向对象设计原则](OO/7_面向对象设计原则.md)
-
-# 📝 编程题
-
-## 🚀 剑指 Offer 编程题
-
-> **数据结构相关**
-
-- [第一节 数组和矩阵](AimForOffer/数据结构相关/1_数组和矩阵.md)
-- [第二节 字符串](AimForOffer/数据结构相关/2_字符串.md)
-- [第三节 链表](AimForOffer/数据结构相关/3_链表.md)
-- [第四节 树](AimForOffer/数据结构相关/4_树.md)
-- [第五节 栈](AimForOffer/数据结构相关/5_栈.md)
-- [第六节 队列](AimForOffer/数据结构相关/6_队列.md)
-- [第七节 堆](AimForOffer/数据结构相关/7_堆.md)
-- [第八节 哈希.](AimForOffer/数据结构相关/8_哈希.md)
-
-> **算法相关**
-
-- [第一节 查找](AimForOffer/算法思想相关/1_查找.md)
-- [第二节 排序](AimForOffer/算法思想相关/2_排序.md)
-- [第三节 动态规划](AimForOffer/算法思想相关/3_动态规划.md)
-- [第四节 搜索](AimForOffer/算法思想相关/4_搜索.md)
-- [第五节 排列组合](AimForOffer/算法思想相关/5_排列组合.md)
-- [第六节 贪心](AimForOffer/算法思想相关/6_贪心.md)
-- [第七节 数学运算](AimForOffer/算法思想相关/7_数学运算.md)
-- [第八节 其他](AimForOffer/算法思想相关/8_其他.md)
-
-## 🚀 LeetCode 编程题
-
-> **数据结构相关**
-
-- [第一节 数组问题](LeetCode/数据结构相关/1_数组问题.md)
-- [第二节 链表问题](LeetCode/数据结构相关/2_链表问题.md)
-- [第三节 栈和队列](LeetCode/数据结构相关/3_栈和队列.md)
-- [第四节 二叉树](LeetCode/数据结构相关/4_二叉树.md)
-- [第五节 字符串](LeetCode/数据结构相关/5_字符串.md)
-- [第六节 哈希](LeetCode/数据结构相关/6_哈希.md)
-- [第七节 图](LeetCode/数据结构相关/7_图.md)
-- [第八节 数据结构设计](LeetCode/数据结构相关/8_数据结构设计.md)
-
-> **算法思想相关**
-
-- [第一节 排序](LeetCode/算法思想相关/1_排序.md)
-- [第二节 分治思想](LeetCode/算法思想相关/2_分治思想.md)
-- [第三节 贪心思想](LeetCode/算法思想相关/3_贪心思想.md)
-- [第四节 LRU](LeetCode/算法思想相关/4_LRU.md)
-- [第五节 DFS](LeetCode/算法思想相关/5_DFS.md)
-- [第六节 回溯法](LeetCode/算法思想相关/6_回溯法.md)
-- [第七节 动态规划](LeetCode/算法思想相关/7_动态规划.md)
-- [第八节 数学问题](LeetCode/算法思想相关/8_数学问题.md)
-
-# 💾 数据库
-
-## ✈️ DataBase
-
-- [第一节 数据库系统原理](DataBase/1_数据库系统原理.md)
-- [第二节 关系数据库设计理论](DataBase/2_关系数据库设计理论.md)
-- [第三节 设计关系型数据库](DataBase/3_设计关系型数据库.md)
-- [第四节 SQL](DataBase/4_SQL.md)
-- [第五节 LeetCode_Database题解](DataBase/5_LeetCode_Database题解.md)
-
-## ✈️ MySQL
-
-- [第一节 锁机制](MySQL/1_锁机制.md)
-- [第二节 事务隔离级别实现](MySQL/2_事务隔离级别实现.md)
-- [第三节 索引](MySQL/3_索引.md)
-- [第四节 MySQL架构](MySQL/4_MySQL架构.md)
-- [第五节 MySQL优化](MySQL/5_MySQL优化.md)
-- [第六节 补充知识](MySQL/6_补充知识.md)
-
-## ✈️ Redis
-
-- [第一节 Redis初探](Redis/1_Redis初探.md)
-- [第二节 Redis持久化](Redis/2_Redis持久化.md)
-- [第三节 Redis复制](Redis/3_Redis复制.md)
-- [第四节 处理系统故障](Redis/4_处理系统故障.md)
-- [第五节 Redis事务](Redis/5_Redis事务.md)
-- [第六节 Redis性能方面注意事项](Redis/6_Redis性能方面注意事项.md)
-- [第七节 降低内存占用](Redis/7_降低内存占用.md)
-- [第八节 简单点赞系统](Redis/8_简单点赞系统)
-
-## ✈️ 海量数据处理
-
-- [第一节 概述](MassDataProcessing/1_概述.md)
-- [第二节 哈希分治](MassDataProcessing/2_哈希分治.md)
-- [第三节 位图](MassDataProcessing/3_位图.md)
-- [第四节 布隆过滤器](MassDataProcessing/4_布隆过滤器.md)
-- [第五节 Trie树](MassDataProcessing/5_Trie树.md)
-- [第六节 数据库](MassDataProcessing/6_数据库.md)
-- [第七节 倒排索引](MassDataProcessing/7_倒排索引.md)
-
-# 🔨 消息中间件
-
-## 🚁 Kafka
-
-- [第一节 消息队列](Kafka/1_消息队列.md)
-- [第二节 Kafka的架构](Kafka/2_Kafka的架构.md)
-- [第三节 Kafka的高可用原理](Kafka/3_Kafka的高可用原理.md)
-- [第四节 Kafka中一些常见问题](Kafka/4_Kafka中一些常见问题.md)
-- [第五节 Kafka特点](Kafka/5_Kafka特点.md)
-
-## 🚁 RabbitMQ
-
-- [第一节 主流消息中间件](RabbitMQ/1_主流消息中间件.md)
-- [第二节 RabbitMQ概述](RabbitMQ/2_RabbitMQ概述.md)
-- [第三节 RabbitMQ入门](RabbitMQ/3_RabbitMQ入门.md)
-- [第四节 RabbitMQ高级特性](RabbitMQ/4_RabbitMQ高级特性.md)
-- [第五节 RabbitMQ整合SpringAMQP](RabbitMQ/5_RabbitMQ整合SpringAMQP.md)
-- [第六节 RabbitMQ整合SpringBoot](RabbitMQ/6_RabbitMQ整合SpringBoot.md)
-- [RabbitMQ 官网](https://www.rabbitmq.com/)
-
-# 📖 系统设计
-
-## ♨️ 常用框架
-
-- [第一节 SpringMVC](SSM/1_SpringMVC.md)
-- [第二节 SpringIOC](SSM/2_SpringIOC.md)
-- [第三节 SpringAOP](SSM/3_SpringAOP.md)
-- [第四节 Spring事务管理](SSM/4_Spring事务管理.md)
-- [第五节 Spring中Bean的作用域](SSM/5_Spring中Bean的作用域.md)
-- [第六节 Spring中Bean的生命周期](SSM/6_Spring中Bean的生命周期.md)
-- [第七节 Spring中常见注解](SSM/7_Spring中常见注解.md)
-- [第八节 Spring中涉及到的设计模式](SSM/8_Spring中涉及到的设计模式.md)
-- [第九节 MyBaits](SSM/9_MyBaits.md)
-
-## ♨️ Web 安全
-
-- [第一节 常见安全问题](Safety/1_常见安全问题.md)
-- [第二节 跨站脚本攻击](Safety/2_跨站脚本攻击.md)
-- [第三节 跨站请求伪造](Safety/3_跨站请求伪造.md)
-- [第四节 Cookies问题](Safety/4_Cookies问题.md)
-- [第五节 点击劫持问题](Safety/5_点击劫持问题.md)
-- [第六节 传输安全](Safety/6_传输安全.md)
-- [第七节 密码安全](Safety/7_密码安全.md)
-- [第八节 接入层注入问题](Safety/8_接入层注入问题.md)
-- [第九节 接入层上传问题](Safety/9_接入层上传问题.md)
-- [第十节 信息泄露](Safety/10_信息泄露.md)
-- [第十一节 DoS攻击](Safety/11_DoS攻击.md)
-- [第十二节 重放攻击](Safety/12_重放攻击.md)
-
-## ♨️ 分布式
-
-- [第一节 分布式系统设计理念](distribution/1_分布式系统设计理念.md)
-- [第二节 CAP理论](distribution/2_CAP理论.md)
-- [第三节 BASE理论](distribution/3_BASE理论.md)
-- [第四节 分布式锁](distribution/4_分布式锁.md)
-- [第五节 分布式事务](distribution/5_分布式事务.md)
-- [第六节 分布式缓存的一致性哈希算法](distribution/6_分布式缓存的一致性哈希算法.md)
-
-## ♨️ 微服务
-
-## ♨️ 网站架构
-
-- [网站架构](web_architecture/1_网站架构.md)
-- [设计秒杀系统](web_architecture/2_设计秒杀系统.md)
-
-# 💻 工具
-
-## 🔑 Git
-
-- [git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
-- [git - 图解](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
-
-## 🔑 Maven
-
-- [Maven](Maven/1_Maven.md)
-- [Maven 下载](http://maven.apache.org/download.cgi)
-- [Maven 官网](https://maven.apache.org/)
-
-## 🔑 Nginx
-
-## 🔑 Docker
-
-
-
-# 🔧 进阶指南
-
-- [后端面试进阶指南](https://xiaozhuanlan.com/CyC2018)
-- [Java 面试进阶指南](https://xiaozhuanlan.com/javainterview)
-- [编码规范指南](https://github.com/alibaba/p3c)
-
-# 🙊 参考资料
-
-- [参考仓库](reference/参考仓库.md)
-- [参考书籍](reference/参考书籍.md)
-- [慕课网](reference/慕课网.md)
diff --git "a/docs/RabbitMQ/1_\344\270\273\346\265\201\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266.md" "b/docs/RabbitMQ/1_\344\270\273\346\265\201\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266.md"
deleted file mode 100644
index c80a19fb..00000000
--- "a/docs/RabbitMQ/1_\344\270\273\346\265\201\346\266\210\346\201\257\344\270\255\351\227\264\344\273\266.md"
+++ /dev/null
@@ -1,34 +0,0 @@
-# 主流消息中间件
-
-## 1. ActiveMQ
-
-ActiveMQ 是 Apache 出品的最流行的、性能强劲的开源消息总线,并且其是一个完全支持 JMS 规范的消息中间件。其丰富的 API、多种集群构建模式使得它成为业界老牌消息中间件,广泛应用于中小型企业。
-
-## 2. Kafka
-
-Kafka 是 LinkedIn 开源的分布式发布-订阅消息系统,目前属于 Apache 顶级项目。
-
-Kafka 主要特点是基于 Pull 的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8 版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。
-
-## 3. RocketMQ
-
-RocketMQ 是阿里开源的消息中间件,目前也已经孵化为 Apache 顶级项目,它是纯 Java 开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。
-
-RocketMQ 思路起源于 Kafka,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog 分发等场景。
-
-## 4. RabbitMQ
-
-RabbitMQ 是使用 Erlang 语言开发的开源消息队列系统,基于 AMQP 协议来实现。
-
-AMQP 的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP 协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
-
-## 对比
-
-| 消息中间件 | ActiveMQ | Kafka | RocketMQ | RabbitMQ |
-| :----------------------: | :-----------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :------------------------------------------------: |
-| *吞吐量* | 万数量级 | 十万级,高吞吐 | 十万级,高吞吐 | 万数量级 |
-| *Topic 数量对吞吐量影响* | / | Topic 从几十到几百时,吞吐量会大幅度下降,
在同等机器下,Kafka 尽量保证 Topic 数量不要过多,如果要支撑大规模的 Topic,需要增加更多的机器 | Topic 可达百/千级,吞吐量下降幅度小
在同等机器下,可以支撑大量的 Topic | / |
-| *时效性* | 毫秒级 | 毫秒级 | 毫秒级 | 微秒级 |
-| *可用性* | 高
基于主从架构实现高可用高 | 非常高
分布式架构 | 非常高
分布式架构 | 高
基于主从架构实现高可用高 |
-| *可靠性* | 丢失数据的概率低 | 优化参数配置,
可以做到零丢失 | 优化参数配置,
可以做到零丢失 | 基本不会丢失数据 |
-| *功能* | 功能较完备 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | MQ 功能较为完善,还是分布式的,扩展性好 | 基于 ErLang 开发,并发能力很强,性能极好,延时很低 |
\ No newline at end of file
diff --git "a/docs/RabbitMQ/2_RabbitMQ\346\246\202\350\277\260.md" "b/docs/RabbitMQ/2_RabbitMQ\346\246\202\350\277\260.md"
deleted file mode 100644
index aeab0f78..00000000
--- "a/docs/RabbitMQ/2_RabbitMQ\346\246\202\350\277\260.md"
+++ /dev/null
@@ -1,256 +0,0 @@
-# RabbitMQ 概述
-
-## 简介
-
-RabbitMQ 是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用中间共享数据,RabbitMQ 是使用 **Erlang 语言**来编写的,并且 RabbitMQ 是基于 AMQP 协议的。
-
-特点:
-
-- 开源、性能优秀,稳定性好
-- 提供可靠性消息投递模式(confirm)、返回模式(return)
-- 与 SpringAOP 完美的整合、API 丰富
-- 集群模式丰富,表达式配置,HA 模式,镜像队列模型
-- 保证数据不丢失的前提做到高可靠性、可用性
-
-RabbitMQ 高性能的原因:
-
-**Erlang 语言**最初用在交换机的架构模式,这样使得 RabbitMQ 在 Broker 之间进行数据交互的性能时非常优秀的。Erlang 的优点:Erlang 有着和原生 Socket 一样的延迟。
-
-## AMQP 协议
-
-AMQP(Advanced Message Queuing Protocol)协议,即高级消息队列协议。
-
-AMQP 是具有现在特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
-
-### 协议模型
-
-
-
-### 核心概念
-
-- Server
-
- 又称 Broker,接受客户端的连接,实现 AMQP 实体服务
-
-- Connection
-
- 连接,应用程序与 Broker 的网络连接
-
-- Channel
-
- 网络通信,几乎所有的操作都在 Channel 中进行,Channel 是进行消息读写的通道。客户端可建立多个 Channel,每个 Channel 代表一个会话任务。
-
-- Message
-
- 消息,服务器和应用程序之间传送的数据,由 Properties 和 Body 组成。Properties 可以对消息进行设置,比如消息的优先级、延迟等高级特性;Body 则就是消息体内容。
-
-- Virtual Host
-
- 虚拟主机,用于进行逻辑隔离,最上层的消息路由。
-
- 一个 Virtual Host 里面可以有若干个 Exchange 和 Queue,同一个 Virtual Host 里面不能有相同名称的 Exchange 或 Queue。
-
-- Exchange
-
- 交换机,接受消息,根据路由键转发消息到绑定的队列。
-
-- Binding
-
- Exchange 和 Queue 之间的虚拟连接,Binding 中可以包含 Routing Key
-
-- Routing Key
-
- 一个路由规则,虚拟机可用它来确定如何路由一个特定消息
-
-- Queue
-
- 也称为 Message Queue(消息队列),保存消息并将它们转发给消费者
-
-
-
-## RabbitMQ 整体架构
-
-
-
-
-
-## RabbitMQ 消息流转
-
-
-
-## RabbitMQ 简单使用
-
-### 1. 启动服务器
-
-```html
-rabbitmq-server start &
-```
-
-### 2. 停止服务器
-
-```html
-rabbitmqctl stop_app
-```
-
-### 3. 插件管理
-
-```html
-rabbitmq-plugins enable rabbitmq_management
-```
-
-### 4. 查看管控台
-
-```html
-http://localhost:15672/
-
-# 用户名 guest
-# 密码 guest
-```
-
-
-
-## 命令行基础操作
-
-### 1. 应用
-
-- 关闭应用
-
- ```html
- rabbitmqctl stop_app
- ```
-
-- 启动应用
-
- ```html
- rabbitmqctl start_app
- ```
-- 查看节点状态
-
- ```html
- rabbitmqctl status
- ```
-
-### 2. 用户
-
-- 添加用户
-
- ```html
- rabbitmqctl add_user username password
- ```
-
-- 删除用户
-
- ```html
- rabbitmqctl delete_user username
- ```
-
-- 列出所有用户
-
- ```html
- rabbitmqctl list_users
- ```
-
-- 清除用户权限
-
- ```html
- rabbitmqctl clear_permissions -p vhostpath username
- ```
-
-- 列出用户权限
-
- ```html
- rabbitmqctl list_user_permissions username
- ```
-
-- 修改密码
-
- ```html
- rabbitmqctl change_password username newpassword
- ```
-
-- 设置用户权限
-
- ```html
- rabbitmqctl set_permissions -p vhostpath username ".*" ".*" ".*"
- ```
-
-### 3. 虚拟主机
-
-- 创建虚拟主机
-
- ```html
- rabbitmqctl add_vhost vhostpath
- ```
-
-- 删除虚拟主机
-
- ```html
- rabbitmqctl delete_vhost vhostpath
- ```
-
-- 列出所有虚拟主机
-
- ```html
- rabbitmqctl list_vhosts
- ```
-
-- 列出虚拟主机上所有权限
-
- ```html
- rabbitmqctl list_permissions -p vhostpath
- ```
-
-### 4. 队列
-
-- 查看所有队列信息
-
- ```html
- rabbitmqctl list_queues
- ```
-
-- 清除队列里的消息
-
- ```html
- rabbitmqctl -p vhostpath purge_queue blue
- ```
-
-
-
-## 命令行高级操作
-
-- 移除所有数据
-
- ```html
- rabbitmqctl reset
- # 要在 rabbitmqctl stop_app 之后使用
- ```
-
-- 组成集群命令
-
- ```html
- rabbitmqctl join_cluster [--ram]
- ```
-
-- 查看集群状态
-
- ```html
- rabbitmq cluster_status
- ```
-
-- 修改集群节点的存储形式
-
- ```html
- rabbitmqctl change_cluser_node_type disc | ram
- ```
-
-- 摘除节点(忘记节点)
-
- ```html
- rabbitmqctl forget_cluster_node [--offline]
- ```
-
-- 修改节点名称
-
- ```html
- rabbitmqctl rename_cluster_node oldnode1 newnode1 [oldnode2] [newnode2]
- ```
diff --git "a/docs/RabbitMQ/3_RabbitMQ\345\205\245\351\227\250.md" "b/docs/RabbitMQ/3_RabbitMQ\345\205\245\351\227\250.md"
deleted file mode 100644
index 2f9b833d..00000000
--- "a/docs/RabbitMQ/3_RabbitMQ\345\205\245\351\227\250.md"
+++ /dev/null
@@ -1,653 +0,0 @@
-# RabbitMQ 入门
-
-## 简单案例:消息生产与消费
-
-pom.xml 配置
-
-```html
-
- com.rabbitmq
- amqp-client
-
- 3.6.5
-
-```
-
-### 生产者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 简单案例:消息生产与消费
- * 消息生产者
- * Created by DHA on 2019/11/18.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 通过 chanel 发送数据
- for(int i=0;i<10;i++){
- String data="Hello!";
- channel.basicPublish("","test001",null,data.getBytes());
- }
-
- //5 关闭相关连接
- channel.close();
- connection.close();
- }
-}
-```
-
-### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 简单案例:消息生产与消费
- * 消息生产者
- * Created by DHA on 2019/11/18.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 通过 chanel 发送数据
- for(int i=0;i<10;i++){
- String data="Hello!";
- channel.basicPublish("","test001",null,data.getBytes());
- }
-
- //5 关闭相关连接
- channel.close();
- connection.close();
- }
-}
-```
-
-## 交换机(Exchange)
-
-交换机:接收消息,并根据路由键转发消息所绑定的队列。
-
-
-
-### 交换机属性
-
-- Name
-
- 交换机名称
-
-- Type
-
- 交换机类型 direct、topic、fanout、headers
-
-- Durability
-
- 是否需要持久化,true 为持久化
-
-- Auto Delete
-
- 当最后一个绑定到 Exchange 上的队列删除后,自动删除该 Exchange
-
-- Internal
-
- 当前 Exchange 是否用于 RabbitMQ 内部使用,默认为 false
-
-- Arguments
-
- 扩展参数,用于扩展 AMQP 协议
-
-### 交换机类型
-
-#### 1. Direct Exchange
-
-所有发送到 Direct Exchange 的消息被转发到 routing key 中指定的 Queue。
-
-
-
-注意:Direct 模式可以使用 RabbitMQ 自带的 Exchange:default Exchange,所以不需要将 Exchange 进行任何绑定(binding)操作,消息传递时,routing key 必须完全匹配才会被队列接收,否则该消息会被抛弃。
-
-##### 生产者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Direct Exchange
- * 所有发送到 Direct Exchange 的消息被转发到 routing key 中指定的 Queue。
- * 消息生产者
- * Created by DHA on 2019/11/19.
- */
-public class Producer4DirectExchange {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- //声明 exchange 名称
- String exchangeName="test_direct_exchange";
- String routingKey = "test.direct";
-
- //5 通过 chanel 发送数据
- String msg = "Hello World RabbitMQ 4 Direct Exchange Message ... ";
- channel.basicPublish(exchangeName, routingKey , null , msg.getBytes());
-
- //6 关闭相关连接
- channel.close();
- connection.close();
- }
-}
-```
-
-
-
-##### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Direct Exchange
- * 所有发送到 Direct Exchange 的消息被转发到 routing key 中指定的 Queue。
- * 消息消费者
- * Created by DHA on 2019/11/19.
- */
-public class Consumer4DirectExchange {
- public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName="test_direct_exchange";
- String exchangeType="direct";
- String queueName="test_direct_queue";
- String routingKey="test.direct";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- //5 创建消费者
- QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
-
- //6 设置 channel
- channel.basicConsume(queueName,true,queueingConsumer);
-
- //7 获取数据
- while(true){
- QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
- String msg=new String(delivery.getBody());
- System.out.println("消费端:"+msg);
- }
- }
-}
-```
-
-
-
-#### 2. Topic Exchange
-
-Topic Exchange 将 routing key 与某 Topic 进行模糊匹配,此时队列需要绑定一个 Topic。
-
-可以使用**通配符**进行模糊匹配:
-
-- "#" 表示匹配一个或多个词。"log.#" 能够匹配到 "log.info.oa"
-- "*" 表示只能匹配一个词。"log.\*" 值能够匹配到 "log.info"
-
-
-
-##### 生产者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Topic Exchange
- * Topic Exchange 将 routing key 与某 Topic 进行模糊匹配,此时队列需要绑定一个 Topic。
- * 消息生产者
- * Created by DHA on 2019/11/19.
- */
-public class Producer4TopicExchange {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- //声明 exchange 名称
- String exchangeName="test_topic_exchange";
- String routingKey1 = "user.save";
- String routingKey2 = "user.update";
- String routingKey3 = "user.delete.abc";
-
- //5 通过 chanel 发送数据
- String msg = "Hello World RabbitMQ 4 Topic Exchange Message ... ";
- channel.basicPublish(exchangeName, routingKey1 , null , msg.getBytes());
- channel.basicPublish(exchangeName, routingKey2 , null , msg.getBytes());
- channel.basicPublish(exchangeName, routingKey3 , null , msg.getBytes());
-
- //6 关闭相关连接
- channel.close();
- connection.close();
- }
-}
-```
-
-
-
-##### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Topic Exchange
- * Topic Exchange 将 routing key 与某 Topic 进行模糊匹配,此时队列需要绑定一个 Topic。
- * 消息消费者
- * Created by DHA on 2019/11/19.
- */
-public class Consumer4TopicExchange {
- public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName="test_topic_exchange";
- String exchangeType="topic";
- String queueName="test_topic_queue";
- String routingKey="user.*";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- //5 创建消费者
- QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
-
- //6 设置 channel
- channel.basicConsume(queueName,true,queueingConsumer);
-
- //7 获取数据
- while(true){
- QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
- String msg=new String(delivery.getBody());
- System.out.println("消费端:"+msg);
- }
- }
-}
-```
-
-
-
-#### 3. Fanout Exchange
-
-Fanout Exchange 不处理 routing key,只需要简单的将队列绑定到交换机上,发送到交换机的消息都会被转发到交换机绑定的所有队列上。
-
-
-
-##### 生产者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Fanout Exchange
- * Fanout Exchange 不处理 routing key,只需要简单的将队列绑定到交换机上,发送到交换机的消息都会被转发到交换机绑定的所有队列上。
- * 消息生产者
- * Created by DHA on 2019/11/19.
- */
-public class Producer4FanoutExchange {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- //声明 exchange 名称
- String exchangeName="test_fanout_exchange";
-
- //5 通过 chanel 发送数据
- for(int i = 0; i < 10; i ++) {
- String msg = "Hello World RabbitMQ 4 Fanout Exchange Message ...";
- channel.basicPublish(exchangeName, "", null , msg.getBytes());
- }
-
- //6 关闭相关连接
- channel.close();
- connection.close();
- }
-}
-```
-
-
-
-##### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Fanout Exchange
- * Fanout Exchange 不处理 routing key,只需要简单的将队列绑定到交换机上,发送到交换机的消息都会被转发到交换机绑定的所有队列上。
- * 消息消费者
- * Created by DHA on 2019/11/19.
- */
-public class Consumer4FanoutExchange {
- public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName="test_fanout_exchange";
- String exchangeType="fanout";
- String queueName="test_fanout_queue";
- String routingKey="";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- //5 创建消费者
- QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
-
- //6 设置 channel
- channel.basicConsume(queueName,true,queueingConsumer);
-
- //7 获取数据
- while(true){
- QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
- String msg=new String(delivery.getBody());
- System.out.println("消费端:"+msg);
- }
- }
-}
-```
-
-
-
-## 绑定(Binding)
-
-绑定是指 Exchange 和 Exchange、Queue 之间的连接关系。Binding 中可以包含 routing key 或者参数。
-
-## 消息队列(Queue)
-
-消息队列实际用来存储消息数据。常用属性:
-
-- Durability
-
- 是否持久化,Durable:是,Transient:否
-
-- Auto Delete
-
- yes 表示最后一个监听被移除之后,会自动删除该 Queue
-
-## 虚拟主机(Virtual Host)
-
-虚拟主机用于进行逻辑隔离,是最长层的消息路由。
-
-- 一个虚拟主机中可以有若干个 Exchange 和 Queue
-- 同一个虚拟主机中不能有相同名称的 Exchange 或 Queue
-
-## 消息(Message)
-
-消息即服务器和应用程序之间传送的数据。本质上就是一段数据,由 Properties 和 Body(Properties)组成。
-
-常用属性:
-
-- Delivery Mode
-
-- Headers
-
- 自定义属性
-
-### 生产者
-
-```java
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 消息属性设置
- * 消息生产者
- * Created by DHA on 2019/11/18.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- // 设置自定义属性
- Map headers = new HashMap<>();
- headers.put("attr1", "111");
- headers.put("attr2", "222");
-
- //4 设置消息属性
- AMQP.BasicProperties properties=new AMQP.BasicProperties.Builder()
- .deliveryMode(2) // 2 表示持久化的投递
- .contentEncoding("UTF-8") // 设置内容编码
- .expiration("10000") // 设置过期时间为 10 秒
- .headers(headers) // 自定义属性
- .build();
-
- //5 通过 chanel 发送数据
- for(int i=0;i<5;i++){
- String data="Hello!";
- channel.basicPublish("","test001",properties,data.getBytes());
- }
-
- //6 关闭相关连接
- channel.close();
- connection.close();
- }
-}
-```
-
-
-
-### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-import com.rabbitmq.client.QueueingConsumer.Delivery;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 消息属性设置
- * 消息消费者
- * Created by DHA on 2019/11/18.
- */
-public class Consumer {
- public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明一个队列
- String queueName="test001";
- channel.queueDeclare(queueName,true,false,false,null);
-
- //5 创建消费者
- QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
-
- //6 设置 channel
- channel.basicConsume(queueName,true,queueingConsumer);
-
- //7 获取数据
- while(true){
- Delivery delivery=queueingConsumer.nextDelivery();
- String msg=new String(delivery.getBody());
- System.out.println("消费端:"+msg);
- // 获取自定义属性数据
- Map headers=delivery.getProperties().getHeaders();
- System.err.println("headers get attribute attr1 value: " + headers.get("attr1"));
- }
- }
-}
-```
-
diff --git "a/docs/RabbitMQ/4_RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/docs/RabbitMQ/4_RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247.md"
deleted file mode 100644
index defefa14..00000000
--- "a/docs/RabbitMQ/4_RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247.md"
+++ /dev/null
@@ -1,1011 +0,0 @@
-# RabbitMQ 高级特性
-
-## 消息100%可靠性投递的解决方案
-
-### 生产端可靠性投递
-
-- 保障消息成功发出
-- 保障 MQ 节点的成功接收
-- 发送端收到 MQ 节点(Broker)确认应答
-- **完善的消息补偿机制**
-
-### 解决方案1:消息落库
-
-消息落库,对消息状态进行打标。
-
-
-
-
-
-### 解决方案2:二次确认,回调检查
-
-消息的延迟投递,做二次确认,回调检查。
-
-
-
-
-
-## 消费端幂等性操作
-
-- 唯一 ID + 指纹码 机制,利用数据库主键去重
-
- 优点:实现简单
-
- 缺点:高并罚下有数据库写入的性能瓶颈
-
- 解决方案:根据 ID 进行分库分表进行算法路由
-
-- 利用 Redis 原子特性实现
-
-## Confirm 消息机制
-
-消息的确认是指生产者投递消息后,如果 Broker 收到消息,则会给生产者一个应答,生产者进行接收应答,用来确定这条消息是否正常地发送到 Broker。
-
-
-
-实现机制:
-
-- 第一步:在 channel 上开启确认模式
-
- ```java
- channel.confirmSelect()
- ```
-
-- 第二步:在 channel 上添加监听
-
- ```java
- channel.addConfirmListener()
- ```
-
- 监听成功和失败的返回结果,根据具体的结果对消息进行重新发送或记录日志等后续处理。
-
-### 生产者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.ConfirmListener;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Confirm 消息机制
- * 消息的确认是指生产者投递消息后,如果 Broker 收到消息,则会给生产者一个应答,生产者进行接收应答,用来确定这条消息是否正常地发送到 Broker。
- * 消息生产者
- * Created by DHA on 2019/11/18.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 指定消息投递模式:confirm 模式
- channel.confirmSelect();
-
- String exchangeName = "test_confirm_exchange";
- String routingKey = "confirm.save";
-
- //5 通过 chanel 发送数据
- String msg="Hello!";
- channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
-
- //6 添加一个确认监听
- channel.addConfirmListener(new ConfirmListener() {
- @Override
- public void handleAck(long l, boolean b) throws IOException {
- System.out.println("------ack!-------");
- }
-
- @Override
- public void handleNack(long l, boolean b) throws IOException {
- System.out.println("------Nack!-------");
- }
- });
- }
-}
-```
-
-
-
-### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-import com.rabbitmq.client.QueueingConsumer.Delivery;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Confirm 消息机制
- * 消息的确认是指生产者投递消息后,如果 Broker 收到消息,则会给生产者一个应答,生产者进行接收应答,用来确定这条消息是否正常地发送到 Broker。
- * 消息消费者
- * Created by DHA on 2019/11/18.
- */
-public class Consumer {
- public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName="test_confirm_exchange";
- String exchangeType="topic";
- String queueName="test_confirm_queue";
- String routingKey="confirm.#";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- //5 创建消费者
- QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
-
- //6 设置 channel
- channel.basicConsume(queueName,queueingConsumer);
-
- //7 获取数据
- while(true){
- Delivery delivery=queueingConsumer.nextDelivery();
- String msg=new String(delivery.getBody());
- System.out.println("消费端:"+msg);
- }
- }
-}
-```
-
-
-
-## Return 消息机制
-
-消息生产者通过制动一个 Exchange 和 routing key,把消息送达到某一个队列中去,然后消费者监听队列,进行消费处理操作。
-
-在某些情况下,如果我们在发送消息的时候,当前的 **Exchange 不存在**或者指定的 **routing key路由不到**,此时我们需要监听这种不可达的消息,就要使用 Return Listener。
-
-基础 API 有一个配置项 mandatory
-
-- 如果为 true,那么监听器会接收到路由不可达的消息,然后进行后续处理
-- 如果为 false, 那么 Broker 端自动删除该消息
-
-
-
-
-
-
-
-### 生产者
-
-```java
-import com.rabbitmq.client.*;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Return 消息机制
- * 消息生产者通过制动一个 Exchange 和 routing key,把消息送达到某一个队列中去,然后消费者监听队列,进行消费处理操作。
- * 在某些情况下,如果我们在发送消息的时候,当前的 Exchange 不存在或者指定的 routing key路由不到,此时我们需要监听这种不可达的消息,就要使用 Return Listener。
- *
- * 消息生产者
- * Created by DHA on 2019/11/18.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 指定消息投递模式:confirmListener 模式
- channel.confirmSelect();
-
- String exchangeName = "test_return_exchange";
- String routingKey = "returnListener.save";
- String routingKeyError = "return.save";
-
- //5 通过 chanel 发送数据
- String msg="Hello!";
- // mandatory 如果为 true,那么监听器会接收到路由不可达的消息,然后进行后续处理
- // mandatory 如果为 false, 那么 Broker 端自动删除该消息
- channel.basicPublish(exchangeName,routingKeyError,true,null,msg.getBytes());
-
- //6 添加一个监听
- channel.addReturnListener(new ReturnListener() {
- @Override
- public void handleReturn(int replyCode, String replyText, String exchange,
- String routingKey, AMQP.BasicProperties properties, byte[] body)
- throws IOException {
- System.err.println("---------handle return----------");
- System.err.println("replyCode: " + replyCode);
- System.err.println("replyText: " + replyText);
- System.err.println("exchange: " + exchange);
- System.err.println("routingKey: " + routingKey);
- System.err.println("properties: " + properties);
- System.err.println("body: " + new String(body));
- }
- });
-
- }
-}
-```
-
-
-
-### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-import com.rabbitmq.client.QueueingConsumer.Delivery;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Return 消息机制
- * 消息生产者通过制动一个 Exchange 和 routing key,把消息送达到某一个队列中去,然后消费者监听队列,进行消费处理操作。
- * 在某些情况下,如果我们在发送消息的时候,当前的 Exchange 不存在或者指定的 routing key路由不到,此时我们需要监听这种不可达的消息,就要使用 Return Listener。
- *
- * 消息消费者
- * Created by DHA on 2019/11/18.
- */
-public class Consumer {
- public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName="test_return_exchange";
- String exchangeType="topic";
- String queueName="test_return_queue";
- String routingKey="returnListener.#";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- //5 创建消费者
- QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
-
- //6 设置 channel
- channel.basicConsume(queueName,queueingConsumer);
-
- //7 获取数据
- while(true){
- Delivery delivery=queueingConsumer.nextDelivery();
- String msg=new String(delivery.getBody());
- System.out.println("消费端:"+msg);
- }
- }
-}
-```
-
-
-
-## 消费端自定义监听
-
-我们一般在代码中编写 while 循环,进行 consumer.nextDelivery 方法获取下一条消息,然后进行消费处理!
-
-但是,我们使用自定义的 Counsumer 更加方便,解耦性更强,在实际工作中广泛使用。
-
-### 自定义消费者
-
-实现步骤:
-
-- 先继承 `com.rabbitmq.client.DefaultConsumer`
-- 再重写 `handleDelivery()` 方法
-
-```java
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.DefaultConsumer;
-import com.rabbitmq.client.Envelope;
-
-import java.io.IOException;
-
-/**
- * 自定义消费者
- * 1 先继承 DefaultConsumer
- * 2 然后重写 handleDelivery() 方法
- *
- * Created by DHA on 2019/11/20.
- */
-public class MyConsumer extends DefaultConsumer{
- public MyConsumer(Channel channel) {
- super(channel);
- }
-
- @Override
- public void handleDelivery(String consumerTag, Envelope envelope,
- AMQP.BasicProperties properties, byte[] body)
- throws IOException {
- System.err.println("----------consumer message-----------");
- System.err.println("consumerTag:"+consumerTag);
- System.err.println("envelope:"+envelope);
- System.err.println("properties:"+properties);
- System.err.println("body:"+new String(body));
- }
-}
-```
-
-
-
-### 生产者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 消费端自定义监听
- * 消息生产者
- * Created by DHA on 2019/11/19.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- //声明 exchange 名称
- String exchangeName="test_consumer_exchange";
- String routingKey = "consumer.save";
-
- //5 通过 chanel 发送数据
- String msg = "Hello World RabbitMQ 4 Consumer Exchange Message ... ";
- channel.basicPublish(exchangeName, routingKey , true,null , msg.getBytes());
- }
-}
-```
-
-
-
-### 消费者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-import com.rabbitmq.client.QueueingConsumer.Delivery;
-
-public class Consumer {
-
- public static void main(String[] args) throws Exception {
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName = "test_consumer_exchange";
- String exchangeType= "topic";
- String routingKey = "consumer.#";
- String queueName = "test_consumer_queue";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- /*
- //5 创建消费者
- QueueingConsumer queueingConsumer=new QueueingConsumer(channel);
-
- //6 设置 channel
- channel.basicConsume(queueName,true,queueingConsumer);
-
- //7 获取数据
- while(true){
- QueueingConsumer.Delivery delivery=queueingConsumer.nextDelivery();
- String msg=new String(delivery.getBody());
- System.out.println("消费端:"+msg);
- }
- */
-
- //5 消费端自定义监听 使用 MyConsumer 相应实例
- channel.basicConsume(queueName, true, new MyConsumer(channel));
- }
-}
-```
-
-
-
-## 消费端限流
-
-RabbitMQ 提供了一种 QoS(服务质量保证) 功能,**在非自动确认消息的前提下**,如果一定数目的消息(通过基于 Consume 或者 Channel 设置 QoS 值)未被确认前,不进行消费新的消息。
-
-涉及到的方法:
-
-```erlang
-void BasicQoS(unit prefetchSize,ushort prefetchCount,bool global)
-```
-
-- prefetchSize:0
-- prefetchCount:告知 RabbitMQ 不要同时给一个消费者推送多个 N 个消息,即一旦有 N 个消息还没有 ACK,则该 Consumer 将 block 掉,一直到有消息 ack
-- golbal:true 表示将上面设置应用于 Channel;true 表示将上面设置应用于 Consumer。
-
-注意:
-
-- prefetchSize 和 global 这两项,RabbitMQ 没有实现,暂且不研究
-- prefetchCount 在 no_ask-false 的情况下生效,即在自动应答的情况下是不生效的
-
-### 生产者
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 消费端限流
- * 消息生产者
- * Created by DHA on 2019/11/19.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- //声明 exchange 名称
- String exchangeName="test_qos_exchange";
- String routingKey = "qos.save";
-
- //5 通过 chanel 发送数据
- for(int i=0;i<5;i++){
- String msg = "Hello World RabbitMQ 4 Qos Message ... ";
- channel.basicPublish(exchangeName, routingKey , true,null , msg.getBytes());
- }
- }
-}
-```
-
-
-
-### 消费者
-
-```java
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.DefaultConsumer;
-import com.rabbitmq.client.Envelope;
-
-import java.io.IOException;
-
-/**
- * 自定义消费者
- * 1 先继承 DefaultConsumer
- * 2 然后重写 handleDelivery() 方法
- *
- * Created by DHA on 2019/11/20.
- */
-public class MyConsumer extends DefaultConsumer{
-
- // channel 进行签收
- private Channel channel;
-
- public MyConsumer(Channel channel) {
- super(channel);
- this.channel=channel;
- }
-
- @Override
- public void handleDelivery(String consumerTag, Envelope envelope,
- AMQP.BasicProperties properties, byte[] body)
- throws IOException {
- System.err.println("----------consumer message-----------");
- System.err.println("consumerTag:"+consumerTag);
- System.err.println("envelope:"+envelope);
- System.err.println("properties:"+properties);
- System.err.println("body:"+new String(body));
-
- // false 表示不支持批量签收
- channel.basicAck(envelope.getDeliveryTag(),false);
- }
-}
-```
-
-
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-/**
- * 消费端限流
- * 消息消费者
- *
- * basicQoS(prefetchSize,refetchCount,global)
- * - prefetchSize:0
- * - prefetchCount:告知 RabbitMQ 不要同时给一个消费者推送多个 N 个消息,即一旦有 N 个消息还没有 ACK,
- * 则该 Consumer 将 block 掉,一直到有消息 ack
- * - golbal:true 表示将上面设置应用于 Channel;true 表示将上面设置应用于 Consumer。
- *
- * Created by DHA on 2019/11/19.
- */
-public class Consumer {
-
- public static void main(String[] args) throws Exception {
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName = "test_qos_exchange";
- String exchangeType= "topic";
- String routingKey = "qos.#";
- String queueName = "test_qos_queue";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- // 第二个参数为 1,表示一次处理一条消息
- // 第三个参数为 false,表示应用到 Consumer 级别
- channel.basicQos(0,1,false);
-
- //5 消费端自定义监听
- // 首先将第二个参数设置为 false,进行手动签收
- channel.basicConsume(queueName, false, new MyConsumer(channel));
- }
-}
-```
-
-
-
-## 消费端 ACK 与重回队列
-
-- **消费端的手工 ACK 和 NACK**
-
- 消费端进行消费时:
-
- 如果由于业务异常,我们可以进行日志的记录,然后进行补偿;
-
- 如果由于服务器宕机等严重问题,那么需要手工进行 ACK 保障消费端消费成功
-
-- **消费端的重回队列**
-
- 消费端重回队列是为了对没有成功的消息, 消息会被重新投递给 Broker。一般在使用应用中,都会关闭重回队列,即设置为 false。
-
-### 生产者
-
-```java
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 消费端的手工 ACK 和 NACK
- * 消息生产者
- *
- * Created by DHA on 2019/11/19.
- */
-public class Producer {
- public static void main(String[] args) throws IOException, TimeoutException {
-
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- //声明 exchange 名称
- String exchangeName="test_ack_exchange";
- String routingKey = "ack.save";
-
- //5 通过 chanel 发送数据
- for(int i =0; i<5; i ++){
-
- Map headers = new HashMap();
- headers.put("num", i);
-
- AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
- .deliveryMode(2)
- .contentEncoding("UTF-8")
- .headers(headers)
- .build();
- String msg = "Hello RabbitMQ ACK Message " + i;
- channel.basicPublish(exchangeName, routingKey, true, properties, msg.getBytes());
- }
- }
-}
-```
-
-
-
-### 消费者
-
-```java
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.DefaultConsumer;
-import com.rabbitmq.client.Envelope;
-
-import java.io.IOException;
-
-/**
- * 消费端的重回队列
- * 消费端重回队列是为了对没有成功的消息, 消息会被重新投递给 Broker。
- * 一般在使用应用中,都会关闭重回队列,即设置为 false。
- *
- * Created by DHA on 2019/11/20.
- */
-public class MyConsumer extends DefaultConsumer{
-
- // channel 进行签收
- private Channel channel;
-
- public MyConsumer(Channel channel) {
- super(channel);
- this.channel=channel;
- }
-
- @Override
- public void handleDelivery(String consumerTag, Envelope envelope,
- AMQP.BasicProperties properties, byte[] body)
- throws IOException {
- System.err.println("-----------consume message----------");
- System.err.println("body: " + new String(body));
-
- // 为了实验效果明显
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- Integer num=(Integer) properties.getHeaders().get("num");
- if(num==0){
- // 第二个参数表示是否支持批量签收,如果为 false,表示不支持批量签收
- // 第三个参数表示是否重回队列,如果为 true,表示支持重回队列,则会重回到队列的尾端
- channel.basicNack(envelope.getDeliveryTag(),false,true);
- }else{
- // false 表示不支持批量签收
- channel.basicAck(envelope.getDeliveryTag(),false);
- }
- //channel.basicAck(envelope.getDeliveryTag(),false);
- }
-}
-```
-
-
-
-```java
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-/**
- * 消费端的手工 ACK 和 NACK
- * 消息消费者
- *
- * Created by DHA on 2019/11/19.
- */
-public class Consumer {
-
- public static void main(String[] args) throws Exception {
- //1 创建一个 Connectionfactory,并进行设置
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- //2 通过连接工厂创建连接
- Connection connection = connectionFactory.newConnection();
-
- //3 通过 connecion 创建一个 Channel
- Channel channel = connection.createChannel();
-
- //4 声明
- String exchangeName = "test_ack_exchange";
- String exchangeType= "topic";
- String routingKey = "ack.#";
- String queueName = "test_ack_queue";
-
- // 声明一个交换机
- channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
- // 声明一个队列
- channel.queueDeclare(queueName,false,false,false,null);
- // 绑定:将一个队列绑定到一个交换机上
- channel.queueBind(queueName,exchangeName,routingKey);
-
- //5 消费端自定义监听
- // 首先将第二个参数 autoACK 设置为 false,进行手动签收
- channel.basicConsume(queueName, false, new MyConsumer(channel));
- }
-}
-```
-
-
-
-## TTL
-
-TTL(Time To Live)即生存时间。
-
-- RabbitMQ 支持**消息**的过期时间,在消息发送时可以进行指定
-
-- RabbitMQ 支持**队列**的过期时间,从消息如队列开始计算,只要超过了队列的超时时间配置,那么会自动清除消息
-
-## 死信队列(DLX,Dead-Letter-Exchange )
-
-利用 DLX,当消息在一个队列中变成死信(dead message)之后,其能被重新 publish 到另一个 Exchange,这个 Exchange 就是 DLX。
-
-消息变成死信的几种情况:
-
-- 消息被拒绝(basic.reject / basic.nack),并且 requeue=false
-- 消息 TTL 过期
-- 队列达到最大长度
-
-注意:
-
-- DLX 也是一个正常的 Exchange,和一般的 Exchange 没有区别,它能在任何队列上被指定,实际上就是设置某个队列的属性。
-
-- 当这个队列中有死信时,RabbitMQ 就会自动的将这个消息重新发布到设置的 Exchange 上去,进而被路由到另一个队列。
-
-- 死信队列设置需要设置 Exchange 和 队列,然后绑定
-
- ```java
- channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
- channel.queueDeclare("dlx.queue", true, false, false, null);
- channel.queueBind("dlx.queue", "dlx.exchange", "#");
- ```
-
- 然后我们进行正常声明 Exchange、队列和绑定,此时需要在队列上加上参数 arguments
-
- ```java
- Map agruments = new HashMap();
- agruments.put("x-dead-letter-exchange", "dlx.exchange");
- //这个agruments属性,要设置到声明队列上
- channel.queueDeclare(queueName, true, false, false, agruments);
- ```
-
-### 生产者
-
-```java
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-/**
- * 死信队列
- * 利用 DLX,当消息在一个队列中变成死信(dead message)之后,
- * 其能被重新 publish 到另一个 Exchange,这个 Exchange 就是 DLX。
- *
- * 消息生产者
- * Created by DHA on 2019/11/20.
- */
-public class Producer {
-
- public static void main(String[] args) throws Exception {
-
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- Connection connection = connectionFactory.newConnection();
- Channel channel = connection.createChannel();
-
- String exchange = "test_dlx_exchange";
- String routingKey = "dlx.save";
-
- String msg = "Hello RabbitMQ DLX Message";
-
- AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
- .deliveryMode(2)
- .contentEncoding("UTF-8")
- .expiration("10000")
- .build();
- channel.basicPublish(exchange, routingKey, true, properties, msg.getBytes());
- }
-}
-```
-
-
-
-### 消费者
-
-```java
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.DefaultConsumer;
-import com.rabbitmq.client.Envelope;
-
-import java.io.IOException;
-
-/**
- * 自定义消费者
- * 1 先继承 DefaultConsumer
- * 2 然后重写 handleDelivery() 方法
- *
- * Created by DHA on 2019/11/20.
- */
-public class MyConsumer extends DefaultConsumer{
- public MyConsumer(Channel channel) {
- super(channel);
- }
-
- @Override
- public void handleDelivery(String consumerTag, Envelope envelope,
- AMQP.BasicProperties properties, byte[] body)
- throws IOException {
- System.err.println("----------consumer message-----------");
- System.err.println("consumerTag:"+consumerTag);
- System.err.println("envelope:"+envelope);
- System.err.println("properties:"+properties);
- System.err.println("body:"+new String(body));
- }
-}
-```
-
-
-
-```java
-import java.util.HashMap;
-import java.util.Map;
-
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.Connection;
-import com.rabbitmq.client.ConnectionFactory;
-
-/**
- * 死信队列
- * 利用 DLX,当消息在一个队列中变成死信(dead message)之后,
- * 其能被重新 publish 到另一个 Exchange,这个 Exchange 就是 DLX。
- *
- * 消息消费者
- * Created by DHA on 2019/11/20.
- */
-public class Consumer {
-
- public static void main(String[] args) throws Exception {
-
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost("localhost");
- connectionFactory.setPort(5672);
- connectionFactory.setVirtualHost("/");
-
- Connection connection = connectionFactory.newConnection();
- Channel channel = connection.createChannel();
-
- // 这就是一个普通的交换机 和 队列 以及路由
- String exchangeName = "test_dlx_exchange";
- String routingKey = "dlx.#";
- String queueName = "test_dlx_queue";
-
- channel.exchangeDeclare(exchangeName, "topic", true, false, null);
- Map agruments = new HashMap();
- agruments.put("x-dead-letter-exchange", "dlx.exchange");
- //这个agruments属性,要设置到声明队列上
- channel.queueDeclare(queueName, true, false,false,agruments);
- channel.queueBind(queueName, exchangeName, routingKey);
-
- //要进行死信队列的声明:
- channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
- channel.queueDeclare("dlx.queue", true, false, false, null);
- channel.queueBind("dlx.queue", "dlx.exchange", "#");
-
- channel.basicConsume(queueName, true, new MyConsumer(channel));
- }
-}
-```
-
-
-
-
-
-
-
diff --git "a/docs/RabbitMQ/5_RabbitMQ\346\225\264\345\220\210SpringAMQP.md" "b/docs/RabbitMQ/5_RabbitMQ\346\225\264\345\220\210SpringAMQP.md"
deleted file mode 100644
index 3385a135..00000000
--- "a/docs/RabbitMQ/5_RabbitMQ\346\225\264\345\220\210SpringAMQP.md"
+++ /dev/null
@@ -1,737 +0,0 @@
-# RabbitMQ 整合 SpringAMQP
-
-## RabbitAdmin
-
-RabbitAdmin 类可以很好地操作 RabbitMQ,在 Spring 中直接进行注入即可。
-
-### 配置
-
-```java
-
- com.rabbitmq
- amqp-client
- 3.6.5
-
-
-
- org.springframework.boot
- spring-boot-starter-amqp
-
-```
-
-
-
-### 注入
-
-```java
-import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
-import org.springframework.amqp.rabbit.connection.ConnectionFactory;
-import org.springframework.amqp.rabbit.core.RabbitAdmin;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-@ComponentScan({"com.southeast.spring.*"})
-public class RabbitMQConfig {
-
- @Bean
- public ConnectionFactory connectionFactory(){
- CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
- connectionFactory.setAddresses("localhost");
- connectionFactory.setUsername("guest");
- connectionFactory.setPassword("guest");
- connectionFactory.setVirtualHost("/");
- return connectionFactory;
- }
-
- @Bean
- public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
- RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
- rabbitAdmin.setAutoStartup(true);
- return rabbitAdmin;
- }
-}
-```
-
-注意:
-
-- autoStartup 必须要设置为 true,否则容器不会加载 RabbitAdmin
-
-### 基础功能操作
-
-```java
-import java.util.HashMap;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.amqp.core.Binding;
-import org.springframework.amqp.core.BindingBuilder;
-import org.springframework.amqp.core.DirectExchange;
-import org.springframework.amqp.core.FanoutExchange;
-import org.springframework.amqp.core.Queue;
-import org.springframework.amqp.core.TopicExchange;
-import org.springframework.amqp.rabbit.core.RabbitAdmin;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-/**
- * RabbitAdmin 测试
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class ApplicationTests {
-
- @Autowired
- private RabbitAdmin rabbitAdmin;
-
- /**
- * 测试 RabbitAdmin 的基本基本功能
- * @throws Exception
- */
- @Test
- public void testAdmin() throws Exception {
- // 声明交换机
- rabbitAdmin.declareExchange(new DirectExchange("test.direct", false, false));
- rabbitAdmin.declareExchange(new TopicExchange("test.topic", false, false));
- rabbitAdmin.declareExchange(new FanoutExchange("test.fanout", false, false));
-
- // 声明队列
- rabbitAdmin.declareQueue(new Queue("test.direct.queue", false));
- rabbitAdmin.declareQueue(new Queue("test.topic.queue", false));
- rabbitAdmin.declareQueue(new Queue("test.fanout.queue", false));
-
- // 声明绑定:注意有 2 种方式
- rabbitAdmin.declareBinding(new Binding("test.direct.queue",
- Binding.DestinationType.QUEUE,
- "test.direct", "direct", new HashMap<>()));
-
- rabbitAdmin.declareBinding(
- BindingBuilder
- .bind(new Queue("test.topic.queue", false)) //直接创建队列
- .to(new TopicExchange("test.topic", false, false)) //直接创建交换机 建立关联关系
- .with("user.#")); //指定路由Key
-
-
- rabbitAdmin.declareBinding(
- BindingBuilder
- .bind(new Queue("test.fanout.queue", false))
- .to(new FanoutExchange("test.fanout", false, false)));
-
- //清空队列数据
- rabbitAdmin.purgeQueue("test.topic.queue", false);
- }
-}
-```
-
-### RabbitAdmin 源码解析
-
-RabbitMQAdmin 底层实现就是从 Spring 容器中获取 Exchange、Binding、routing key 以及 Queue 的 @Bean 声明,然后使用 RabbitTemplate 的 execute 方法执行对应的声明、修改、删除等一系列 RabbitMQ 基础功能操作。
-
-```java
-this.rabbitTemplate.execute(
- new ChannelCallback