diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/00.Java\345\274\200\345\217\221\347\216\257\345\242\203.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/00.Java\345\274\200\345\217\221\347\216\257\345\242\203.md" index 990924e89e..0bff5680ac 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/00.Java\345\274\200\345\217\221\347\216\257\345\242\203.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/00.Java\345\274\200\345\217\221\347\216\257\345\242\203.md" @@ -1,6 +1,7 @@ --- title: Java 开发环境 date: 2018-08-29 17:28:34 +order: 00 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/01.Java\345\237\272\347\241\200\350\257\255\346\263\225.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/01.Java\345\237\272\347\241\200\350\257\255\346\263\225.md" index ae7a7ff144..494adc56b8 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/01.Java\345\237\272\347\241\200\350\257\255\346\263\225.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/01.Java\345\237\272\347\241\200\350\257\255\346\263\225.md" @@ -1,6 +1,7 @@ --- title: Java 基础语法特性 date: 2022-01-25 07:31:16 +order: 01 categories: - Java - JavaSE @@ -37,17 +38,17 @@ public class HelloWorld { ## 基本数据类型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java基本数据类型.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java基本数据类型.svg) -> 👉 扩展阅读:[深入理解 Java 基本数据类型](https://dunwu.github.io/blog/pages/55d693/) +> 👉 扩展阅读:[深入理解 Java 基本数据类型](https://dunwu.github.io/waterdrop/pages/55d693/) -## 变量 +## 变量和常量 Java 支持的变量类型有: - `局部变量` - 类方法中的变量。 -- `实例变量(也叫成员变量)` - 类方法外的变量,不过没有 `static` 修饰。 -- `类变量(也叫静态变量)` - 类方法外的变量,用 `static` 修饰。 +- `成员变量(也叫实例变量)` - 类方法外的变量,不过没有 `static` 修饰。 +- `静态变量(也叫类变量)` - 类方法外的变量,用 `static` 修饰。 特性对比: @@ -74,72 +75,72 @@ Java 支持的变量类型有: ## 数组 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java数组.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java数组.svg) -> 👉 扩展阅读:[深入理解 Java 数组](https://dunwu.github.io/blog/pages/155518/) +> 👉 扩展阅读:[深入理解 Java 数组](https://dunwu.github.io/waterdrop/pages/155518/) ## 枚举 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java枚举.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java枚举.svg) -> 👉 扩展阅读:[深入理解 Java 枚举](https://dunwu.github.io/blog/pages/979887/) +> 👉 扩展阅读:[深入理解 Java 枚举](https://dunwu.github.io/waterdrop/pages/979887/) ## 操作符 Java 中支持的操作符类型如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java操作符.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java操作符.svg) > 👉 扩展阅读:[Java 操作符](http://www.runoob.com/java/java-operators.html) ## 方法 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220125072221.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220125072221.png) -> 👉 扩展阅读:[深入理解 Java 方法](https://dunwu.github.io/blog/pages/7a3ffc/) +> 👉 扩展阅读:[深入理解 Java 方法](https://dunwu.github.io/waterdrop/pages/7a3ffc/) ## 控制语句 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java控制语句.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java控制语句.svg) -> 👉 扩展阅读:[Java 控制语句](https://dunwu.github.io/blog/pages/fb4f8c/) +> 👉 扩展阅读:[Java 控制语句](https://dunwu.github.io/waterdrop/pages/fb4f8c/) ## 异常 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java异常框架.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java异常框架.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java异常.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java异常.svg) -> 👉 扩展阅读:[深入理解 Java 异常](https://dunwu.github.io/blog/pages/37415c/) +> 👉 扩展阅读:[深入理解 Java 异常](https://dunwu.github.io/waterdrop/pages/37415c/) ## 泛型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java泛型.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java泛型.svg) -> 👉 扩展阅读:[深入理解 Java 泛型](https://dunwu.github.io/blog/pages/33a820/) +> 👉 扩展阅读:[深入理解 Java 泛型](https://dunwu.github.io/waterdrop/pages/33a820/) ## 反射 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java反射.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java反射.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java代理.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java代理.svg) -> 👉 扩展阅读:[深入理解 Java 反射和动态代理](https://dunwu.github.io/blog/pages/0d066a/) +> 👉 扩展阅读:[深入理解 Java 反射和动态代理](https://dunwu.github.io/waterdrop/pages/0d066a/) ## 注解 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/注解简介.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/注解简介.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/元注解.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/元注解.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/内置注解.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/内置注解.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/自定义注解.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/自定义注解.svg) -> 👉 扩展阅读:[深入理解 Java 注解](https://dunwu.github.io/blog/pages/ecc011/) +> 👉 扩展阅读:[深入理解 Java 注解](https://dunwu.github.io/waterdrop/pages/ecc011/) ## 序列化 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java序列化.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java序列化.svg) -> 👉 扩展阅读:[Java 序列化](https://dunwu.github.io/blog/pages/2b2f0f/) \ No newline at end of file +> 👉 扩展阅读:[Java 序列化](https://dunwu.github.io/waterdrop/pages/2b2f0f/) diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/02.Java\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/02.Java\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" index 2cec70caee..ea9aaa1a80 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/02.Java\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/02.Java\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 基本数据类型 date: 2019-05-06 15:02:02 +order: 02 categories: - Java - JavaSE @@ -14,7 +15,7 @@ permalink: /pages/55d693/ # 深入理解 Java 基本数据类型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220408172602.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220408172602.png) ## 数据类型分类 diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/03.Java\351\235\242\345\220\221\345\257\271\350\261\241.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/03.Java\351\235\242\345\220\221\345\257\271\350\261\241.md" index 160c927371..59871ff0fe 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/03.Java\351\235\242\345\220\221\345\257\271\350\261\241.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/03.Java\351\235\242\345\220\221\345\257\271\350\261\241.md" @@ -1,6 +1,7 @@ --- title: Java 面向对象 date: 2020-08-06 18:20:39 +order: 03 categories: - Java - JavaSE @@ -14,20 +15,20 @@ permalink: /pages/3e1661/ # Java 面向对象 -> 在[Java 基本数据类型](docs/01.Java/01.JavaSE/01.基础特性/02.Java基本数据类型.md) 中我们了解 Java 中支持的基本数据类型(值类型)。本文开始讲解 Java 中重要的引用类型——类。 +> 在[Java 基本数据类型](02.Java基本数据类型.md) 中我们了解 Java 中支持的基本数据类型(值类型)。本文开始讲解 Java 中重要的引用类型——类。 ## 面向对象 每种编程语言,都有自己的操纵内存中元素的方式。 -Java 中提供了[基本数据类型](https://dunwu.github.io/blog/pages/55d693/),但这还不能满足编写程序时,需要抽象更加复杂数据类型的需要。因此,Java 中,允许开发者通过类(类的机制下面会讲到)创建自定义类型。 +Java 中提供了[基本数据类型](https://dunwu.github.io/waterdrop/pages/55d693/),但这还不能满足编写程序时,需要抽象更加复杂数据类型的需要。因此,Java 中,允许开发者通过类(类的机制下面会讲到)创建自定义类型。 有了自定义类型,那么数据类型自然会千变万化,所以,必须要有一定的机制,使得它们仍然保持一些必要的、通用的特性。 Java 世界有一句名言:一切皆为对象。这句话,你可能第一天学 Java 时,就听过了。这不仅仅是一句口号,也体现在 Java 的设计上。 - 首先,所有 Java 类都继承自 `Object` 类(从这个名字,就可见一斑)。 -- 几乎所有 Java 对象初始化时,都要使用 `new` 创建对象([基本数据类型](https://dunwu.github.io/blog/pages/55d693/)、String、枚举特殊处理),对象存储在堆中。 +- 几乎所有 Java 对象初始化时,都要使用 `new` 创建对象([基本数据类型](https://dunwu.github.io/waterdrop/pages/55d693/)、String、枚举特殊处理),对象存储在堆中。 ```java // 下面两 @@ -69,7 +70,7 @@ String s = new String("abc"); 狗和鸟都是动物。如果将狗、鸟作为类,它们可以继承动物类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1552641712126.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1552641712126.png) 类的继承形式: @@ -128,7 +129,7 @@ Java 中提供的基本数据类型,只能表示单一的数值,这用于数 类的形式如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1552640231731.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1552640231731.png) ## 方法 diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/04.Java\346\226\271\346\263\225.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/04.Java\346\226\271\346\263\225.md" index 364ca50924..3d24d5c97e 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/04.Java\346\226\271\346\263\225.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/04.Java\346\226\271\346\263\225.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 方法 date: 2019-05-06 15:02:02 +order: 04 categories: - Java - JavaSE @@ -474,7 +475,7 @@ public class MethodOverloadDemo { ## 小结 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553767582595.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553767582595.png) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/05.Java\346\225\260\347\273\204.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/05.Java\346\225\260\347\273\204.md" index 0026b3759e..7d1c14b53a 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/05.Java\346\225\260\347\273\204.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/05.Java\346\225\260\347\273\204.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 数组 date: 2019-05-06 15:02:02 +order: 05 categories: - Java - JavaSE @@ -61,7 +62,7 @@ Java 数组在内存中的存储是这样的: 如下图所示:只有当 JVM 执行 `new String[]` 时,才会在堆中开辟相应的内存区域。数组对象 array 可以视为一个指针,指向这块内存的存储地址。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1552473482942.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1552473482942.png) ## 声明数组 @@ -374,7 +375,7 @@ Java 中,提供了一个很有用的数组工具类:Arrays。 ## 小结 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553753908349.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553753908349.png) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/06.Java\346\236\232\344\270\276.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/06.Java\346\236\232\344\270\276.md" index 5212295c53..54854d93a3 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/06.Java\346\236\232\344\270\276.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/06.Java\346\236\232\344\270\276.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 枚举 date: 2019-05-06 15:02:02 +order: 06 categories: - Java - JavaSE @@ -657,7 +658,7 @@ public class EnumMapDemo { ## 小结 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553002212154.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553002212154.png) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/07.Java\346\216\247\345\210\266\350\257\255\345\217\245.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/07.Java\346\216\247\345\210\266\350\257\255\345\217\245.md" index 04770e7e10..dd3530490d 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/07.Java\346\216\247\345\210\266\350\257\255\345\217\245.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/07.Java\346\216\247\345\210\266\350\257\255\345\217\245.md" @@ -1,6 +1,7 @@ --- title: Java 控制语句 date: 2020-10-17 19:13:25 +order: 07 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/08.Java\345\274\202\345\270\270.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/08.Java\345\274\202\345\270\270.md" index 58ebd37dc5..7962559380 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/08.Java\345\274\202\345\270\270.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/08.Java\345\274\202\345\270\270.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 异常 date: 2019-05-06 15:02:02 +order: 08 categories: - Java - JavaSE @@ -14,7 +15,7 @@ permalink: /pages/37415c/ # 深入理解 Java 异常 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553752019030.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553752019030.png) ## 异常框架 @@ -63,7 +64,7 @@ Exception 又分为可检查(checked)异常和不检查(unchecked)异常 - `NoSuchFieldException` - 请求的变量不存在。 - `NoSuchMethodException` - 请求的方法不存在。 -示例: +【示例】`Exception` 示例 ```java public class ExceptionDemo { @@ -75,7 +76,7 @@ public class ExceptionDemo { 试图编译运行时会报错: -``` +```java Error:(7, 47) java: 未报告的异常错误java.lang.NoSuchMethodException; 必须对其进行捕获或声明以便抛出 ``` @@ -85,7 +86,7 @@ Error:(7, 47) java: 未报告的异常错误java.lang.NoSuchMethodException; 必 **编译器不会检查 `RuntimeException` 异常。**当程序中可能出现这类异常时,倘若既没有通过 `throws` 声明抛出它,也没有用 `try catch` 语句捕获它,程序还是会编译通过。 -示例: +【示例】`RuntimeException` 示例 ```java public class RuntimeExceptionDemo { @@ -100,7 +101,7 @@ public class RuntimeExceptionDemo { 运行时输出: -``` +```java Exception in thread "main" java.lang.ArithmeticException: / by zero at io.github.dunwu.javacore.exception.RumtimeExceptionDemo01.main(RumtimeExceptionDemo01.java:6) ``` @@ -124,11 +125,11 @@ Exception in thread "main" java.lang.ArithmeticException: / by zero ## 自定义异常 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553752795010.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553752795010.png) **自定义一个异常类,只需要继承 `Exception` 或 `RuntimeException` 即可。** -示例: +【示例】自定义异常示例 ```java public class MyExceptionDemo { @@ -146,7 +147,7 @@ public class MyExceptionDemo { 输出: -``` +```java Exception in thread "main" io.github.dunwu.javacore.exception.MyExceptionDemo$MyException: 自定义异常 at io.github.dunwu.javacore.exception.MyExceptionDemo.main(MyExceptionDemo.java:9) ``` @@ -157,7 +158,7 @@ Exception in thread "main" io.github.dunwu.javacore.exception.MyExceptionDemo$My 如果一个方法没有捕获一个检查性异常,那么该方法必须使用 `throws` 关键字来声明。`throws` 关键字放在方法签名的尾部。 -`throw` 示例: +【示例】`throw` 示例 ```java public class ThrowDemo { @@ -183,7 +184,7 @@ java.lang.RuntimeException: 抛出一个异常 也可以使用 `throw` 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。 -`throws` 示例: +【示例】`throws` 示例 ```java public class ThrowsDemo { @@ -217,8 +218,8 @@ public class ThrowsDemo { 输出: -``` -反射获取 digits 方法成功 +```java +// 反射获取 digits 方法成功 java.lang.NoSuchMethodException: java.lang.String.toString(int) at java.lang.Class.getMethod(Class.java:1786) at io.github.dunwu.javacore.exception.ThrowsDemo.f1(ThrowsDemo.java:12) @@ -226,14 +227,14 @@ java.lang.NoSuchMethodException: java.lang.String.toString(int) at io.github.dunwu.javacore.exception.ThrowsDemo.main(ThrowsDemo.java:30) ``` -throw 和 throws 的区别: +`throw` 和 `throws` 的区别: -- throws 使用在函数上,throw 使用在函数内。 -- throws 后面跟异常类,可以跟多个,用逗号区别;throw 后面跟的是异常对象。 +- `throws` 使用在函数上,`throw` 使用在函数内。 +- `throws` 后面跟异常类,可以跟多个,用逗号区别;`throw` 后面跟的是异常对象。 ## 捕获异常 -**使用 try 和 catch 关键字可以捕获异常**。try catch 代码块放在异常可能发生的地方。 +**使用 try 和 catch 关键字可以捕获异常**。`try catch` 代码块放在异常可能发生的地方。 它的语法形式如下: @@ -261,13 +262,19 @@ try { } ``` -- `try` - **`try` 语句用于监听。将要被监听的代码(可能抛出异常的代码)放在 `try` 语句块之内,当 `try` 语句块内发生异常时,异常就被抛出。** -- `catch` - `catch` 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,`try` 后面的 `catch` 块就会被检查。 -- `finally` - **`finally` 语句块总是会被执行,无论是否出现异常。**`try catch` 语句后不一定非要`finally` 语句。`finally` 常用于这样的场景:由于`finally` 语句块总是会被执行,所以那些在 `try` 代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在`finally` 语句块中释放资源。 -- `try`、`catch`、`finally` 三个代码块中的局部变量不可共享使用。 -- `catch` 块尝试捕获异常时,是按照 `catch` 块的声明顺序从上往下寻找的,一旦匹配,就不会再向下执行。因此,如果同一个 `try` 块下的多个 `catch` 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面。 +`try`、`catch`、`finally` 使用要点如下: + +- `try` - **`try` 语句用于监听**。将要被监听的代码(可能抛出异常的代码)放在 `try` 语句块之内,当 `try` 语句块内发生异常时,异常就被抛出。 -示例: +- `catch` - **`catch` 语句包含要捕获异常类型的声明**。当保护代码块中发生一个异常时,`try` 后面的 `catch` 块就会被检查。 + +- `finally` - **`finally` 语句块总是会被执行,无论是否出现异常。**`try catch` 语句后不一定非要 `finally` 语句。`finally` 常用于这样的场景:由于 `finally` 语句块总是会被执行,所以那些在 `try` 代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在 `finally` 语句块中释放资源。 + +- **`try`、`catch`、`finally` 三个代码块中的局部变量不可共享使用**。 + +- `catch` 块尝试捕获异常时,是按照 `catch` 块的声明顺序依次寻找的,一旦匹配,就不会再向下执行。因此,如果同一个 `try` 块下的多个 `catch` 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面。 + +【示例】`try`、`catch`、`finally` 使用示例 ```java public class TryCatchFinallyDemo { @@ -288,9 +295,9 @@ public class TryCatchFinallyDemo { 运行时输出: -``` -出现异常了:java.lang.ArithmeticException: / by zero -不管是否出现异常,都执行此代码 +```java +// 出现异常了:java.lang.ArithmeticException: / by zero +// 不管是否出现异常,都执行此代码 ``` ## 异常链 @@ -301,7 +308,7 @@ public class TryCatchFinallyDemo { 我们有两种方式处理异常,一是 `throws` 抛出交给上级处理,二是 `try…catch` 做具体处理。`try…catch` 的 `catch` 块我们可以不需要做任何处理,仅仅只用 throw 这个关键字将我们封装异常信息主动抛出来。然后在通过关键字 `throws` 继续抛出该方法异常。它的上层也可以做这样的处理,以此类推就会产生一条由异常构成的异常链。 -【示例】 +【示例】异常链示例 ```java public class ExceptionChainDemo { @@ -337,7 +344,7 @@ public class ExceptionChainDemo { 输出: -``` +```java Exception in thread "main" io.github.dunwu.javacore.exception.ExceptionChainDemo$MyException2: 出现 MyException2 at io.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:29) at io.github.dunwu.javacore.exception.ExceptionChainDemo.main(ExceptionChainDemo.java:34) @@ -355,11 +362,11 @@ Caused by: io.github.dunwu.javacore.exception.ExceptionChainDemo$MyException1: ### finally 覆盖异常 -Java 异常处理中 `finally` 中的 `return` 会覆盖 `catch` 代码块中的 `return` 语句和 `throw` 语句,所以 Java **不建议在 `finally` 中使用 `return` 语句**。 +Java 异常处理中 `finally` 中的 `return` 会覆盖 `catch` 代码块中的 `return` 语句和 `throw` 语句,所以**不建议在 `finally` 中使用 `return` 语句**。 此外 `finally` 中的 `throw` 语句也会覆盖 `catch` 代码块中的 `return` 语句和 `throw` 语句。 -示例: +【示例】`finally` 覆盖示例 ```java public class FinallyOverrideExceptionDemo { @@ -381,15 +388,14 @@ public class FinallyOverrideExceptionDemo { } } } +// 输出:C ``` -输出:C - ### 覆盖抛出异常的方法 -当子类重写父类带有 `throws` 声明的函数时,其 `throws` 声明的异常必须在父类异常的可控范围内——用于处理父类的 `throws` 方法的异常处理器,必须也适用于子类的这个带 `throws` 方法 。这是为了支持多态。 +当子类重写父类带有 `throws` 声明的函数时,其 `throws` 声明的异常必须在父类异常的可控范围内;用于处理父类的 `throws` 方法的异常处理器,必须也适用于子类的这个带 `throws` 方法——这是为了支持多态。 -示例: +【示例】覆盖抛出异常示例 ```java public class ExceptionOverrideDemo { @@ -429,7 +435,7 @@ public class ExceptionOverrideDemo { ## 最佳实践 -- 对可恢复的情况使用检查性异常(Exception),对编程错误使用运行时异常(RuntimeException)。 +- 对可恢复的情况使用检查性异常 `Exception`;对编程错误使用运行时异常`RuntimeException`。 - 优先使用 Java 标准的异常。 - 抛出与抽象相对应的异常。 - 在细节消息中包含能捕获失败的信息。 @@ -438,11 +444,10 @@ public class ExceptionOverrideDemo { - 尽量不要在 `finally` 块抛出异常或者返回值。 - 不要忽略异常,一旦捕获异常,就应该处理,而非丢弃。 - 异常处理效率很低,所以不要用异常进行业务逻辑处理。 -- 各类异常必须要有单独的日志记录,将异常分级,分类管理,因为有的时候仅仅想给第三方运维看到逻辑异常,而不是更细节的信息。 -- 如何对异常进行分类: - - 逻辑异常,这类异常用于描述业务无法按照预期的情况处理下去,属于用户制造的意外。 - - 代码错误,这类异常用于描述开发的代码错误,例如 NPE,ILLARG,都属于程序员制造的 BUG。 - - 专有异常,多用于特定业务场景,用于描述指定作业出现意外情况无法预先处理。 +- 各类异常必须要有单独的日志记录,将异常分级,分类管理,因为有的时候仅仅想给第三方运维看到逻辑异常,而不是更细节的信息。如何对异常进行分类: + - **逻辑异常** - 这类异常用于描述业务无法按照预期的情况处理下去,属于用户制造的意外。 + - **代码错误** - 这类异常用于描述开发的代码错误,例如 NPE,ILLARG,都属于程序员制造的 BUG。 + - **专有异常** - 多用于特定业务场景,用于描述指定作业出现意外情况无法预先处理。 > 扩展阅读: > @@ -457,4 +462,4 @@ public class ExceptionOverrideDemo { - [优雅的处理你的 Java 异常](https://my.oschina.net/c5ms/blog/1827907) - https://juejin.im/post/5b6d61e55188251b38129f9a#heading-17 - https://www.cnblogs.com/skywang12345/p/3544168.html -- http://www.importnew.com/26613.html \ No newline at end of file +- http://www.importnew.com/26613.html diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/09.Java\346\263\233\345\236\213.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/09.Java\346\263\233\345\236\213.md" index 4713af4d75..d7270eeea1 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/09.Java\346\263\233\345\236\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/09.Java\346\263\233\345\236\213.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 泛型 date: 2020-10-17 19:13:25 +order: 09 categories: - Java - JavaSE @@ -14,11 +15,13 @@ permalink: /pages/33a820/ # 深入理解 Java 泛型 -## 为什么需要泛型 +## 什么是泛型 -**JDK5 引入了泛型机制**。 +**Java 泛型(generics)是 JDK 5 中引入的特性**。 -为什么需要泛型呢?回答这个问题前,先让我们来看一个示例。 +为什么要引入泛型机制呢? + +回答这个问题前,先让我们来看一个示例。 ```java public class NoGenericsDemo { @@ -50,19 +53,17 @@ public class NoGenericsDemo { // at io.github.dunwu.javacore.generics.NoGenericsDemo.main(NoGenericsDemo.java:23) ``` -> 示例说明: -> -> 在上面的示例中,`List` 容器没有指定存储数据类型,这种情况下,可以向 `List` 添加任意类型数据,编译器不会做类型检查,而是默默的将所有数据都转为 `Object`。 -> -> 假设,最初我们希望向 `List` 存储的是整形数据,假设,某个家伙不小心存入了其他数据类型。当你试图从容器中取整形数据时,由于 `List` 当成 `Object` 类型来存储,你不得不使用类型强制转换。在运行时,才会发现 `List` 中数据不存储一致的问题,这就为程序运行带来了很大的风险(无形伤害最为致命)。 +示例说明: -而泛型的出现,解决了类型安全问题。 +在上面的示例中,`List` 容器没有指定存储数据类型,这种情况下,可以向 `List` 添加任意类型数据,编译器不会做类型检查,而是默默的将所有数据都转为 `Object`。 -泛型具有以下优点: +假设,最初我们希望向 `List` 存储的是整形数据,假设,某个家伙不小心存入了其他数据类型。当你试图从容器中取整形数据时,由于 `List` 当成 `Object` 类型来存储,你不得不使用类型强制转换。在运行时,才会发现 `List` 中数据不存储一致的问题,这就为程序运行带来了很大的风险(无形伤害最为致命)。 -- **编译时的强类型检查** +引入泛型机制,正是为了解决这种类型安全问题。**“泛型”提供了编译时类型安全检测机制,该机制会在编译时检测到非法的类型**。 -泛型要求在声明时指定实际数据类型,Java 编译器在编译时会对泛型代码做强类型检查,并在代码违反类型安全时发出告警。早发现,早治理,把隐患扼杀于摇篮,在编译时发现并修复错误所付出的代价远比在运行时小。 +泛型具有以下**优点**: + +- **编译时的强类型检查** - 泛型要求在声明时指定实际数据类型,Java 编译器在编译时会对泛型代码做强类型检查,并在代码违反类型安全时发出告警。早发现,早治理,把隐患扼杀于摇篮,在编译时发现并修复错误所付出的代价远比在运行时小。 - **避免了类型转换** @@ -82,13 +83,11 @@ list.add("hello"); String s = list.get(0); // no cast ``` -- **泛型编程可以实现通用算法** - -通过使用泛型,程序员可以实现通用算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读。 +- **泛型编程可以实现通用算法** - 通过使用泛型,程序员可以实现通用算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读。 -## 泛型类型 +## 泛型声明 -**`泛型类型`是被参数化的类或接口。** +**“泛型类型”是被参数化的类或接口。** ### 泛型类 @@ -282,9 +281,9 @@ public class GenericsInterfaceDemo02 implements Content { // ABC ``` -## 泛型方法 +### 泛型方法 -泛型方法是引入其自己的类型参数的方法。泛型方法可以是普通方法、静态方法以及构造方法。 +**泛型方法是引入其自己的类型参数的方法**。泛型方法可以是普通方法、静态方法以及构造方法。 泛型方法语法形式如下: @@ -296,7 +295,7 @@ public T func(T obj) {} 泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际类型参数的占位符。 -**使用泛型方法的时候,通常不必指明类型参数,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。类型推断只对赋值操作有效,其他时候并不起作用**。如果将一个返回类型为 T 的泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行推断。编译器会认为:调用泛型方法后,其返回值被赋给一个 Object 类型的变量。 +**使用泛型方法的时候,通常不必指明类型参数,因为编译器会为我们找出具体的类型,这称为“类型参数推断(type argument inference)”**。类型推断只对赋值操作有效,其他时候并不起作用。如果将一个返回类型为 `T` 的泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行推断。编译器会认为:调用泛型方法后,其返回值被赋给一个 Object 类型的变量。 ```java public class GenericsMethodDemo01 { @@ -314,7 +313,7 @@ public class GenericsMethodDemo01 { // class java.lang.Integer ``` -泛型方法中也可以使用可变参数列表 +泛型方法中也可以使用可变参数列表。示例如下: ```java public class GenericVarargsMethodDemo { @@ -336,13 +335,15 @@ public class GenericVarargsMethodDemo { // [A, B, C] ``` -## 类型擦除 +## 泛型要点 + +### 类型擦除 -Java 语言引入泛型是为了在编译时提供更严格的类型检查,并支持泛型编程。不同于 C++ 的模板机制,**Java 泛型是使用类型擦除来实现的,使用泛型时,任何具体的类型信息都被擦除了**。 +Java 语言引入泛型是为了在编译时提供更严格的类型检查,并支持泛型编程。不同于 C++ 的模板机制,**Java 泛型是使用“类型擦除”来实现的,使用泛型时,任何具体的类型信息都被擦除了**。 那么,类型擦除做了什么呢?它做了以下工作: -- 把泛型中的所有类型参数替换为 Object,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法。 +- 把泛型中的所有类型参数替换为 `Object`,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法。 - 擦除出现的类型声明,即去掉 `<>` 的内容。比如 `T get()` 方法声明就变成了 `Object get()` ;`List` 就变成了 `List`。如有必要,插入类型转换以保持类型安全。 - 生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类;因此,泛型不会产生运行时开销。 @@ -362,23 +363,23 @@ public class GenericsErasureTypeDemo { // class java.util.ArrayList ``` -> 示例说明: -> -> 上面的例子中,虽然指定了不同的类型参数,但是 list1 和 list2 的类信息却是一样的。 -> -> 这是因为:**使用泛型时,任何具体的类型信息都被擦除了**。这意味着:`ArrayList` 和 `ArrayList` 在运行时,JVM 将它们视为同一类型。 +示例说明: + +上面的例子中,虽然指定了不同的类型参数,但是 list1 和 list2 的类信息却是一样的。 + +这是因为:**使用泛型时,任何具体的类型信息都被擦除了**。这意味着:`ArrayList` 和 `ArrayList` 在运行时,JVM 将它们视为同一类型。 Java 泛型的实现方式不太优雅,但这是因为泛型是在 JDK5 时引入的,为了兼容老代码,必须在设计上做一定的折中。 -## 泛型和继承 +### 泛型和继承 -**泛型不能用于显式地引用运行时类型的操作之中,例如:转型、instanceof 操作和 new 表达式。因为所有关于参数的类型信息都丢失了**。当你在编写泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已。 +**泛型不能用于显式地引用运行时类型的操作之中**(例如:转型、`instanceof` 操作和 `new` 表达式),因为所有关于参数的类型信息都丢失了。当你在编写泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已。 正是由于泛型时基于类型擦除实现的,所以,**泛型类型无法向上转型**。 > 向上转型是指用子类实例去初始化父类,这是面向对象中多态的重要表现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553147778883.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553147778883.png) `Integer` 继承了 `Object`;`ArrayList` 继承了 `List`;但是 `List` 却并非继承了 `List`。 @@ -389,9 +390,9 @@ List list = new ArrayList<>(); List list2 = list; // Erorr ``` -## 类型边界 +### 类型边界 -有时您可能希望限制可在参数化类型中用作类型参数的类型。**`类型边界`可以对泛型的类型参数设置限制条件**。例如,对数字进行操作的方法可能只想接受 `Number` 或其子类的实例。 +有时您可能希望限制可在参数化类型中用作类型参数的类型。**“类型边界”可以对泛型的类型参数设置限制条件**。例如,对数字进行操作的方法可能只想接受 `Number` 或其子类的实例。 要声明有界类型参数,请列出类型参数的名称,然后是 `extends` 关键字,后跟其限制类或接口。 @@ -461,11 +462,11 @@ public class GenericsExtendsDemo02 { ## 类型通配符 -`类型通配符`一般是使用 `?` 代替具体的类型参数。例如 `List` 在逻辑上是 `List` ,`List` 等所有 `List<具体类型实参>` 的父类。 +**“类型通配符”一般使用 `?` 代替具体的类型参数**。例如 `List` 在逻辑上是 `List` ,`List` 等所有 `List<具体类型实参>` 的父类。 ### 上界通配符 -可以使用**`上界通配符`**来缩小类型参数的类型范围。 +可以使用“上界通配符”来缩小类型参数的类型范围。 它的语法形式为:`` @@ -490,7 +491,7 @@ public class GenericsUpperBoundedWildcardDemo { ### 下界通配符 -**`下界通配符`**将未知类型限制为该类型的特定类型或超类类型。 +**“下界通配符”将未知类型限制为该类型的特定类型或超类类型**。 > 🔔 注意:**上界通配符和下界通配符不能同时使用**。 @@ -546,7 +547,7 @@ public class GenericsUnboundedWildcardDemo { ### 通配符和向上转型 -前面,我们提到:**泛型不能向上转型。但是,我们可以通过使用通配符来向上转型**。 +前面,我们提到:**泛型不能直接向上转型;但是,我们可以通过使用通配符来间接向上转型**。 ```java public class GenericsWildcardDemo { @@ -644,14 +645,14 @@ public class Example { ### 泛型命名 -泛型一些约定俗成的命名: +Java 泛型有一些约定俗成的命名: -- E - Element -- K - Key -- N - Number -- T - Type -- V - Value -- S,U,V etc. - 2nd, 3rd, 4th types +- `E` - Element +- `K` - Key +- `N` - Number +- `T` - Type +- `V` - Value +- `S`、`U`、`V` - 2nd, 3rd, 4th types ### 使用泛型的建议 @@ -662,14 +663,10 @@ public class Example { - 利用有限制通配符来提升 API 的灵活性 - 优先考虑类型安全的异构容器 -## 小结 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java泛型.svg) - ## 参考资料 - [Java 编程思想](https://book.douban.com/subject/2130190/) - [Java 核心技术(卷 1)](https://book.douban.com/subject/3146174/) - [Effective java](https://book.douban.com/subject/3360807/) - [Oracle 泛型文档](https://docs.oracle.com/javase/tutorial/java/generics/index.html) -- [Java 泛型详解](https://juejin.im/post/584d36f161ff4b006cccdb82) \ No newline at end of file +- [Java 泛型详解](https://juejin.im/post/584d36f161ff4b006cccdb82) diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/10.Java\345\217\215\345\260\204.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/10.Java\345\217\215\345\260\204.md" index 68e791365f..109f0a5234 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/10.Java\345\217\215\345\260\204.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/10.Java\345\217\215\345\260\204.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 反射和动态代理 date: 2020-06-04 13:51:01 +order: 10 categories: - Java - JavaSE @@ -17,7 +18,7 @@ permalink: /pages/0d066a/ ## 反射简介 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java反射.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java反射.svg) ### 什么是反射 @@ -44,7 +45,7 @@ permalink: /pages/0d066a/ ### 类加载过程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553611895164.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553611895164.png) 类加载的完整过程如下: @@ -552,7 +553,7 @@ public class ReflectMethodConstructorDemo { 实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了上面提到的反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)、Javassist 等。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/Java代理.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/Java代理.svg) ### 静态代理 @@ -607,7 +608,7 @@ class Proxy extends Subject { 在运行状态中,需要代理的地方,根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,这样就可以避免了 Proxy 角色的 class 在系统中冗杂的问题了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553614585028.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553614585028.png) Java 动态代理基于经典代理模式,引入了一个 `InvocationHandler`,`InvocationHandler` 负责统一管理所有的方法调用。 diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/11.Java\346\263\250\350\247\243.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/11.Java\346\263\250\350\247\243.md" index a202641365..c65e9b89ad 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/11.Java\346\263\250\350\247\243.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/11.Java\346\263\250\350\247\243.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 注解 date: 2019-05-06 15:02:02 +order: 11 categories: - Java - JavaSE @@ -817,13 +818,13 @@ public class RegexValidDemo { ## 小结 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/注解简介.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/注解简介.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/元注解.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/元注解.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/内置注解.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/内置注解.svg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/xmind/自定义注解.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/xmind/自定义注解.svg) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/41.Java\345\270\270\347\224\250\345\267\245\345\205\267\347\261\273.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/41.Java\345\270\270\347\224\250\345\267\245\345\205\267\347\261\273.md" index 83614fc2da..f6b21af1e6 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/41.Java\345\270\270\347\224\250\345\267\245\345\205\267\347\261\273.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/41.Java\345\270\270\347\224\250\345\267\245\345\205\267\347\261\273.md" @@ -1,6 +1,7 @@ --- title: Java 常用工具类 date: 2019-12-16 16:59:15 +order: 41 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/42.JavaString\347\261\273\345\236\213.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/42.JavaString\347\261\273\345\236\213.md" index 4e9a4f56ea..b3c2b58975 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/42.JavaString\347\261\273\345\236\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/42.JavaString\347\261\273\345\236\213.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java String 类型 date: 2020-12-25 18:43:11 +order: 42 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/README.md" "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/README.md" index f98c157abb..16f8b899f3 100644 --- "a/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/README.md" +++ "b/source/_posts/01.Java/01.JavaSE/01.\345\237\272\347\241\200\347\211\271\346\200\247/README.md" @@ -10,6 +10,7 @@ tags: - JavaSE permalink: /pages/8ea213/ hidden: true +index: false --- # Java 基础特性 @@ -28,7 +29,7 @@ hidden: true - [Java 控制语句](07.Java控制语句.md) - [Java 异常](08.Java异常.md) - [Java 泛型](09.Java泛型.md) -- [Java 反射](01.Java反射.md) +- [Java 反射](10.Java反射.md) - [Java 注解](11.Java注解.md) - [Java String 类型](42.JavaString类型.md) @@ -69,4 +70,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/01.Java\346\255\243\345\210\231.md" "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/01.Java\346\255\243\345\210\231.md" index 16aeae674c..8108839c28 100644 --- "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/01.Java\346\255\243\345\210\231.md" +++ "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/01.Java\346\255\243\345\210\231.md" @@ -1,6 +1,7 @@ --- title: Java 正则从入门到精通 date: 2020-12-25 18:43:11 +order: 01 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/02.Java\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/02.Java\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" index 884382e362..2a2c987776 100644 --- "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/02.Java\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" +++ "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/02.Java\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" @@ -1,6 +1,7 @@ --- title: Java 编码和加密 date: 2021-05-24 15:41:47 +order: 02 categories: - Java - JavaSE @@ -278,7 +279,7 @@ HmacSHA512: 6n77htTZ_atc04-SsmxhSK3wzh1sAmdudCl0Cb_RZp4DpienG4LZkhXMbq8lcK7XSnz6 签名时要使用私钥和待签名数据,验证时则需要公钥、签名值和待签名数据,其核心算法主要是消息摘要算法。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/java-message-digest-process.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/java-message-digest-process.jpg) 数字签名常用算法:**RSA**、**DSA**、**ECDSA** @@ -398,11 +399,11 @@ public class DsaCoder { 一种是把明文信息划分为不同的组(或块)结构,分别对每个组(或块)进行加密、解密,称为分组密码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/symmetric-encryption.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/symmetric-encryption.png) 假设甲乙方作为通信双方。假定甲乙双方在消息传递前已商定加密算法,欲完成一次消息传递需要经过如下步骤。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/symmetric-encryption-progress.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/symmetric-encryption-progress.png) #### 对称加密工作模式 @@ -648,7 +649,7 @@ PBE 没有密钥概念,密钥在其他对称加密算法中是经过计算得 流程: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/password-based-encryption-progress.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/password-based-encryption-progress.png) 步骤如下: @@ -790,7 +791,7 @@ public class PBECoder { - 优点:非对称加密算法解决了对称加密算法的密钥分配问题,并极大地提高了算法安全性。 - 缺点:算法比对称算法更复杂,因此加密、解密速度都比对称算法慢很多。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/asymmetric-encryption.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/asymmetric-encryption.png) 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。 diff --git "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/03.Java\345\233\275\351\231\205\345\214\226.md" "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/03.Java\345\233\275\351\231\205\345\214\226.md" index 32ddae3757..1d7264d8a3 100644 --- "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/03.Java\345\233\275\351\231\205\345\214\226.md" +++ "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/03.Java\345\233\275\351\231\205\345\214\226.md" @@ -1,6 +1,7 @@ --- title: Java 国际化 date: 2017-11-08 14:38:26 +order: 03 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/04.JDK8.md" "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/04.JDK8.md" index e38ae25f3c..ca89cdfb75 100644 --- "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/04.JDK8.md" +++ "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/04.JDK8.md" @@ -1,6 +1,7 @@ --- title: JDK8 入门指南 date: 2019-05-06 15:02:02 +order: 04 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/05.JavaSPI.md" "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/05.JavaSPI.md" index bb12bfbe7b..c55e5ae675 100644 --- "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/05.JavaSPI.md" +++ "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/05.JavaSPI.md" @@ -1,6 +1,7 @@ --- title: 源码级深度理解 Java SPI date: 2022-04-26 19:11:59 +order: 05 categories: - Java - JavaSE @@ -299,7 +300,7 @@ private S nextService() { 学习过 JVM 的读者,想必都了解过类加载器的**双亲委派模型(Parents Delegation Model)**。双亲委派模型要求除了顶层的 **`BootstrapClassLoader`** 外,其余的类加载器都应有自己的父类加载器。这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。双亲委派继承体系图如下: -img +img 双亲委派机制约定了:**一个类加载器首先将类加载请求传送到父类加载器,只有当父类加载器无法完成类加载请求时才尝试加载**。 @@ -364,7 +365,7 @@ Class.forName("com.mysql.jdbc.Driver") - Mysql:在 mysql 的 Java 驱动包 `mysql-connector-java-XXX.jar` 中,可以找到 `META-INF/services` 目录,该目录下会有一个名字为`java.sql.Driver` 的文件,文件内容是 `com.mysql.cj.jdbc.Driver`。 `com.mysql.cj.jdbc.Driver` 正是 Mysql 版的 `java.sql.Driver` 实现。如下图所示: - ![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220505201455.png) + ![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220505201455.png) - PostgreSQL 实现:在 PostgreSQL 的 Java 驱动包 `postgresql-42.0.0.jar` 中,也可以找到同样的配置文件,文件内容是 `org.postgresql.Driver`,`org.postgresql.Driver` 正是 PostgreSQL 版的 `java.sql.Driver` 实现。 @@ -799,19 +800,19 @@ Spring Boot 有各种 starter 包,可以根据实际项目需要,按需取 从 spring-boot-autoconfigure 包的结构来看,它有一个 `META-INF/spring.factories` ,显然利用了 Spring Boot SPI,来自动装配其中的配置类。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220505004100.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220505004100.png) 下图是 spring-boot-autoconfigure 的 `META-INF/spring.factories` 文件的部分内容,可以看到其中注册了一长串会被自动加载的 `AutoConfiguration` 类。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220505005130.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220505005130.png) 以 `RedisAutoConfiguration` 为例,这个配置类中,会根据 `@ConditionalXXX` 中的条件去决定是否实例化对应的 Bean,实例化 Bean 所依赖的重要参数则通过 `RedisProperties` 传入。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220505005548.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220505005548.png) `RedisProperties` 中维护了 Redis 连接所需要的关键属性,只要在 yml 或 properties 配置文件中,指定 spring.redis 开头的属性,都会被自动装载到 `RedisProperties` 实例中。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220505005836.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220505005836.png) 通过以上分析,已经一步步解读出 Spring Boot 自动装载的原理。 diff --git "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/99.Java\347\274\226\347\250\213\350\247\204\350\214\203.md" "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/99.Java\347\274\226\347\250\213\350\247\204\350\214\203.md" index b97169bdae..55e9628bb1 100644 --- "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/99.Java\347\274\226\347\250\213\350\247\204\350\214\203.md" +++ "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/99.Java\347\274\226\347\250\213\350\247\204\350\214\203.md" @@ -1,6 +1,7 @@ --- title: Java 编程规范 date: 2019-05-06 15:02:02 +order: 99 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/README.md" "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/README.md" index aca84a0a1b..00a1b36f49 100644 --- "a/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/README.md" +++ "b/source/_posts/01.Java/01.JavaSE/02.\351\253\230\347\272\247\347\211\271\346\200\247/README.md" @@ -10,6 +10,7 @@ tags: - JavaSE permalink: /pages/016137/ hidden: true +index: false --- # Java 高级特性 @@ -61,4 +62,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/01.Java\345\256\271\345\231\250\347\256\200\344\273\213.md" "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/01.Java\345\256\271\345\231\250\347\256\200\344\273\213.md" index 80788a3ff3..f6e17b1d17 100644 --- "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/01.Java\345\256\271\345\231\250\347\256\200\344\273\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/01.Java\345\256\271\345\231\250\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: Java 容器简介 date: 2019-12-29 21:49:58 +order: 01 categories: - Java - JavaSE @@ -14,7 +15,7 @@ permalink: /pages/1cadba/ # Java 容器简介 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221175550.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221175550.png) ## 容器简介 @@ -29,11 +30,11 @@ Java 中常用的存储容器就是数组和容器,二者有以下区别: - **数组可以存储基本数据类型,也可以存储引用数据类型**; - **容器只能存储引用数据类型**,基本数据类型的变量要转换成对应的包装类才能放入容器类中。 -> :bulb: 不了解什么是基本数据类型、引用数据类型、包装类这些概念,可以参考:[Java 基本数据类型](https://dunwu.github.io/blog/pages/55d693/) +> :bulb: 不了解什么是基本数据类型、引用数据类型、包装类这些概念,可以参考:[Java 基本数据类型](https://dunwu.github.io/waterdrop/pages/55d693/) ### 容器框架 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/container/java-container-structure.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/container/java-container-structure.png) Java 容器框架主要分为 `Collection` 和 `Map` 两种。其中,`Collection` 又分为 `List`、`Set` 以及 `Queue`。 @@ -69,7 +70,7 @@ list.add("123"); list.add(123); ``` -> :bulb: 想深入了解 Java 泛型技术的用法和原理可以参考:[深入理解 Java 泛型](https://dunwu.github.io/blog/pages/33a820/) +> :bulb: 想深入了解 Java 泛型技术的用法和原理可以参考:[深入理解 Java 泛型](https://dunwu.github.io/waterdrop/pages/33a820/) ### Iterable 和 Iterator @@ -123,7 +124,7 @@ public interface Iterable { **迭代器模式** - **提供一种方法顺序访问一个聚合对象中各个元素,而又无须暴露该对象的内部表示**。
- +
示例:迭代器遍历 @@ -283,7 +284,7 @@ fail-fast 有两种解决方案: 为了在并发环境下安全地使用容器,Java 提供了同步容器和并发容器。 -> 同步容器和并发容器详情请参考:[Java 并发容器](https://dunwu.github.io/blog/pages/b067d6/) +> 同步容器和并发容器详情请参考:[Java 并发容器](https://dunwu.github.io/waterdrop/pages/b067d6/) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/02.Java\345\256\271\345\231\250\344\271\213List.md" "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/02.Java\345\256\271\345\231\250\344\271\213List.md" index e4964b6825..e2e9597be4 100644 --- "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/02.Java\345\256\271\345\231\250\344\271\213List.md" +++ "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/02.Java\345\256\271\345\231\250\344\271\213List.md" @@ -1,6 +1,7 @@ --- title: Java 容器之 List date: 2018-06-27 23:12:18 +order: 02 categories: - Java - JavaSE @@ -43,7 +44,7 @@ permalink: /pages/69deb2/ > ArrayList 从数据结构角度来看,可以视为支持动态扩容的线性表。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220529190340.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220529190340.png) ### ArrayList 要点 @@ -89,7 +90,7 @@ private int size; - 存储元素的 `Object` 数组(即 `elementData`)使用 `transient` 修饰,使得它可以被 Java 序列化所忽略。 - `ArrayList` 重写了 `writeObject()` 和 `readObject()` 来控制序列化数组中有元素填充那部分内容。 -> :bulb: 不了解 Java 序列化方式,可以参考:[Java 序列化](https://dunwu.github.io/blog/pages/2b2f0f/) +> :bulb: 不了解 Java 序列化方式,可以参考:[Java 序列化](https://dunwu.github.io/waterdrop/pages/2b2f0f/) #### ArrayList 构造方法 @@ -260,7 +261,7 @@ private void writeObject(java.io.ObjectOutputStream s) > LinkedList 从数据结构角度来看,可以视为双链表。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220529190416.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220529190416.png) ### LinkedList 要点 diff --git "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/03.Java\345\256\271\345\231\250\344\271\213Map.md" "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/03.Java\345\256\271\345\231\250\344\271\213Map.md" index 4c379b8f65..c16207d133 100644 --- "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/03.Java\345\256\271\345\231\250\344\271\213Map.md" +++ "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/03.Java\345\256\271\345\231\250\344\271\213Map.md" @@ -1,6 +1,7 @@ --- title: Java 容器之 Map date: 2019-12-29 21:49:58 +order: 03 categories: - Java - JavaSE @@ -19,7 +20,7 @@ permalink: /pages/385755/ ### Map 架构
- +
Map 家族主要成员功能如下: @@ -318,7 +319,7 @@ HashMap **计算桶下标(index)公式:`key.hashCode() ^ (h >>> 16)`**。 在 `get` 和 `put` 的过程中,计算下标时,先对 `hashCode` 进行 `hash` 操作,然后再通过 `hash` 值进一步计算下标,如下图所示:
- +
在对 `hashCode()` 计算 hash 时具体实现是这样的: @@ -365,19 +366,19 @@ static final int hash(Object key) { 怎么理解呢?例如我们从 16 扩展为 32 时,具体的变化如下所示:
- +
因此元素在重新计算 hash 之后,因为 n 变为 2 倍,那么 n-1 的 mask 范围在高位多 1bit(红色),因此新的 index 就会发生这样的变化:
- +
因此,我们在扩充 HashMap 的时候,不需要重新计算 hash,只需要看看原来的 hash 值新增的那个 bit 是 1 还是 0 就好了,是 0 的话索引没变,是 1 的话索引变成“原索引+oldCap”。可以看看下图为 16 扩充为 32 的 resize 示意图:
- +
这个设计确实非常的巧妙,既省去了重新计算 hash 值的时间,而且同时,由于新增的 1bit 是 0 还是 1 可以认为是随机的,因此 resize 的过程,均匀的把之前的冲突的节点分散到新的 bucket 了。 @@ -646,8 +647,8 @@ private void deleteEntry(Entry p) { root = replacement; else if (p == p.parent.left) p.parent.left = replacement; - else - D:\codes\zp\java\database\docs\redis\分布式锁.md p.parent.right = replacement; + else + p.parent.right = replacement; // Null out links so they are OK to use by fixAfterDeletion. p.left = p.right = p.parent = null; @@ -735,15 +736,15 @@ WeakHashMap 的 key 是**弱键**,即是 WeakReference 类型的;ReferenceQu ### Map 简介 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221162002.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221162002.png) ### HashMap -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221162111.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221162111.png) ### 其他 Map -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221161913.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221161913.png) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/04.Java\345\256\271\345\231\250\344\271\213Set.md" "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/04.Java\345\256\271\345\231\250\344\271\213Set.md" index ede08271d1..1586caffb7 100644 --- "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/04.Java\345\256\271\345\231\250\344\271\213Set.md" +++ "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/04.Java\345\256\271\345\231\250\344\271\213Set.md" @@ -1,6 +1,7 @@ --- title: Java 容器之 Set date: 2019-12-29 21:49:58 +order: 04 categories: - Java - JavaSE @@ -17,7 +18,7 @@ permalink: /pages/794c6b/ ## Set 简介
- +
Set 家族成员简介: @@ -238,7 +239,7 @@ public abstract class EnumSet> extends AbstractSet ## 要点总结 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221190717.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221190717.png) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/05.Java\345\256\271\345\231\250\344\271\213Queue.md" "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/05.Java\345\256\271\345\231\250\344\271\213Queue.md" index 8f5d95862b..5573baa923 100644 --- "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/05.Java\345\256\271\345\231\250\344\271\213Queue.md" +++ "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/05.Java\345\256\271\345\231\250\344\271\213Queue.md" @@ -1,6 +1,7 @@ --- title: Java 容器之 Queue date: 2020-02-21 16:26:21 +order: 05 categories: - Java - JavaSE @@ -17,7 +18,7 @@ permalink: /pages/ffa963/ ## Queue 简介
- +
### Queue 接口 diff --git "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/06.Java\345\256\271\345\231\250\344\271\213Stream.md" "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/06.Java\345\256\271\345\231\250\344\271\213Stream.md" index 3ba183d294..072e85eff3 100644 --- "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/06.Java\345\256\271\345\231\250\344\271\213Stream.md" +++ "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/06.Java\345\256\271\345\231\250\344\271\213Stream.md" @@ -1,6 +1,7 @@ --- title: Java 容器之 Stream date: 2020-12-05 18:30:22 +order: 06 categories: - Java - JavaSE @@ -30,7 +31,7 @@ permalink: /pages/529fad/ ## Stream 源码实现 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201205174140.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201205174140.jpg) `BaseStream` 和 `Stream` 是最顶层的接口类。`BaseStream` 主要定义了流的基本接口方法,例如,spliterator、isParallel 等;`Stream` 则定义了一些流的常用操作方法,例如,map、filter 等。 diff --git "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/README.md" "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/README.md" index 27dcd4b31a..11aa4da034 100644 --- "a/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/README.md" +++ "b/source/_posts/01.Java/01.JavaSE/03.\345\256\271\345\231\250/README.md" @@ -11,6 +11,7 @@ tags: - 容器 permalink: /pages/9eb49b/ hidden: true +index: false --- # Java 容器 @@ -54,4 +55,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/01.JavaSE/04.IO/01.JavaIO\346\250\241\345\236\213.md" "b/source/_posts/01.Java/01.JavaSE/04.IO/01.JavaIO\346\250\241\345\236\213.md" index d2b53befd3..8b28621582 100644 --- "a/source/_posts/01.Java/01.JavaSE/04.IO/01.JavaIO\346\250\241\345\236\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/04.IO/01.JavaIO\346\250\241\345\236\213.md" @@ -1,6 +1,7 @@ --- title: Java IO 模型 date: 2020-11-21 16:36:40 +order: 01 categories: - Java - JavaSE @@ -48,19 +49,19 @@ UNIX 系统下的 I/O 模型有 5 种: 用户线程发起 read 调用后就阻塞了,让出 CPU。内核等待网卡数据到来,把数据从网卡拷贝到内核空间,接着把数据拷贝到用户空间,再把用户线程叫醒。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201121163321.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201121163321.jpg) ### 同步非阻塞 I/O 用户线程不断的发起 read 调用,数据没到内核空间时,每次都返回失败,直到数据到了内核空间,这一次 read 调用后,在等待数据从内核空间拷贝到用户空间这段时间里,线程还是阻塞的,等数据到了用户空间再把线程叫醒。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201121163344.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201121163344.jpg) ### I/O 多路复用 用户线程的读取操作分成两步了,线程先发起 select 调用,目的是问内核数据准备好了吗?等内核把数据准备好了,用户线程再发起 read 调用。在等待数据从内核空间拷贝到用户空间这段时间里,线程还是阻塞的。那为什么叫 I/O 多路复用呢?因为一次 select 调用可以向内核查多个数据通道(Channel)的状态,所以叫多路复用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201121163408.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201121163408.jpg) ### 信号驱动 I/O @@ -70,7 +71,7 @@ UNIX 系统下的 I/O 模型有 5 种: 用户线程发起 read 调用的同时注册一个回调函数,read 立即返回,等内核将数据准备好后,再调用指定的回调函数完成处理。在这个过程中,用户线程一直没有阻塞。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201121163428.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201121163428.jpg) ## Java I/O 模型 @@ -145,7 +146,7 @@ BIO 中操作的流主要有两大类,字节流和字符流,两类根据流 - 输入字符流:`Reader` - 输出字符流:`Writer` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200219130627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200219130627.png) ### 字节流 @@ -153,7 +154,7 @@ BIO 中操作的流主要有两大类,字节流和字符流,两类根据流 字节流有两个核心抽象类:`InputStream` 和 `OutputStream`。所有的字节流类都继承自这两个抽象类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200219133627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200219133627.png) #### 文件字节流 @@ -346,7 +347,7 @@ public class PipedStreamDemo { **ObjectInputStream 和 ObjectOutputStream 是对象输入输出流,一般用于对象序列化。** -这里不展开叙述,想了解详细内容和示例可以参考:[Java 序列化](java-serialization.md) +这里不展开叙述,想了解详细内容和示例可以参考:[Java 序列化](03.Java序列化.md) #### 数据操作流 @@ -464,7 +465,7 @@ public class SequenceInputStreamDemo { 字符流有两个核心类:`Reader` 类和 `Writer` 。所有的字符流类都继承自这两个抽象类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200219133648.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200219133648.png) #### 文件字符流 diff --git a/source/_posts/01.Java/01.JavaSE/04.IO/02.JavaNIO.md b/source/_posts/01.Java/01.JavaSE/04.IO/02.JavaNIO.md index 5163f32305..2002864db1 100644 --- a/source/_posts/01.Java/01.JavaSE/04.IO/02.JavaNIO.md +++ b/source/_posts/01.Java/01.JavaSE/04.IO/02.JavaNIO.md @@ -1,6 +1,7 @@ --- title: Java NIO date: 2020-02-19 18:54:21 +order: 02 categories: - Java - JavaSE @@ -386,11 +387,11 @@ BIO 与 NIO 最重要的区别是数据打包和传输的方式:**BIO 以流 BIO 模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630212345.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630212345.png) NIO 模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630212248.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630212248.png) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/04.IO/03.Java\345\272\217\345\210\227\345\214\226.md" "b/source/_posts/01.Java/01.JavaSE/04.IO/03.Java\345\272\217\345\210\227\345\214\226.md" index f037f18bd3..294464fb8e 100644 --- "a/source/_posts/01.Java/01.JavaSE/04.IO/03.Java\345\272\217\345\210\227\345\214\226.md" +++ "b/source/_posts/01.Java/01.JavaSE/04.IO/03.Java\345\272\217\345\210\227\345\214\226.md" @@ -1,6 +1,7 @@ --- title: 深入理解 Java 序列化 date: 2019-05-09 19:06:05 +order: 03 categories: - Java - JavaSE @@ -17,7 +18,7 @@ permalink: /pages/2b2f0f/ > **_关键词:`Serializable`、`serialVersionUID`、`transient`、`Externalizable`、`writeObject`、`readObject`_** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220626163533.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220626163533.png) ## 序列化简介 @@ -26,7 +27,7 @@ permalink: /pages/2b2f0f/ - **序列化(serialize)**:序列化是将对象转换为二进制数据。 - **反序列化(deserialize)**:反序列化是将二进制数据转换为对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619110947.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619110947.png) **序列化用途** @@ -112,7 +113,7 @@ public class SerializeDemo01 { JDK 的序列化过程是怎样完成的呢? -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619111512.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619111512.png) 图来自 RPC 实战与核心原理 - 序列化 diff --git "a/source/_posts/01.Java/01.JavaSE/04.IO/04.Java\347\275\221\347\273\234\347\274\226\347\250\213.md" "b/source/_posts/01.Java/01.JavaSE/04.IO/04.Java\347\275\221\347\273\234\347\274\226\347\250\213.md" index ef4500d5f8..7453b04638 100644 --- "a/source/_posts/01.Java/01.JavaSE/04.IO/04.Java\347\275\221\347\273\234\347\274\226\347\250\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/04.IO/04.Java\347\275\221\347\273\234\347\274\226\347\250\213.md" @@ -1,6 +1,7 @@ --- title: Java 网络编程 date: 2020-02-19 18:54:21 +order: 04 categories: - Java - JavaSE diff --git "a/source/_posts/01.Java/01.JavaSE/04.IO/05.JavaIO\345\267\245\345\205\267\347\261\273.md" "b/source/_posts/01.Java/01.JavaSE/04.IO/05.JavaIO\345\267\245\345\205\267\347\261\273.md" index 3b82609590..b819f6fad3 100644 --- "a/source/_posts/01.Java/01.JavaSE/04.IO/05.JavaIO\345\267\245\345\205\267\347\261\273.md" +++ "b/source/_posts/01.Java/01.JavaSE/04.IO/05.JavaIO\345\267\245\345\205\267\347\261\273.md" @@ -1,6 +1,7 @@ --- title: Java IO 工具类 date: 2020-06-30 21:34:59 +order: 05 categories: - Java - JavaSE diff --git a/source/_posts/01.Java/01.JavaSE/04.IO/README.md b/source/_posts/01.Java/01.JavaSE/04.IO/README.md index 25279d6d3c..762a012e80 100644 --- a/source/_posts/01.Java/01.JavaSE/04.IO/README.md +++ b/source/_posts/01.Java/01.JavaSE/04.IO/README.md @@ -11,6 +11,7 @@ tags: - IO permalink: /pages/e285c8/ hidden: true +index: false --- # Java IO @@ -21,19 +22,19 @@ hidden: true > 关键词:`InputStream`、`OutputStream`、`Reader`、`Writer` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630202823.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630202823.png) ### [Java NIO](02.JavaNIO.md) > 关键词:`Channel`、`Buffer`、`Selector`、`非阻塞`、`多路复用` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630203739.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630203739.png) ### [Java 序列化](03.Java序列化.md) > 关键词:`Serializable`、`serialVersionUID`、`transient`、`Externalizable`、`writeObject`、`readObject` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630204142.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630204142.png) ### [Java 网络编程](04.Java网络编程.md) @@ -50,4 +51,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/01.Java\345\271\266\345\217\221\347\256\200\344\273\213.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/01.Java\345\271\266\345\217\221\347\256\200\344\273\213.md" index 4553f092d3..662dc1fa3b 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/01.Java\345\271\266\345\217\221\347\256\200\344\273\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/01.Java\345\271\266\345\217\221\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: Java并发简介 date: 2019-05-06 15:33:13 +order: 01 categories: - Java - JavaSE @@ -18,7 +19,7 @@ permalink: /pages/f6b642/ > > **摘要**:并发编程并非 Java 语言所独有,而是一种成熟的编程范式,Java 只是用自己的方式实现了并发工作模型。学习 Java 并发编程,应该先熟悉并发的基本概念,然后进一步了解并发的特性以及其特性所面临的问题。掌握了这些,当学习 Java 并发工具时,才会明白它们各自是为了解决什么问题,为什么要这样设计。通过这样由点到面的学习方式,更容易融会贯通,将并发知识形成体系化。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701113445.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701113445.png) ## 并发概念 @@ -71,7 +72,7 @@ permalink: /pages/f6b642/ - 进程是一个实体,拥有独立的资源;而同一个进程中的多个线程共享进程的资源。

- +

JVM 在单个进程中运行,JVM 中的线程共享属于该进程的堆。这就是为什么几个线程可以访问同一个对象。线程共享堆并拥有自己的堆栈空间。这是一个线程如何调用一个方法以及它的局部变量是如何保持线程安全的。但是堆不是线程安全的并且为了线程安全必须进行同步。 @@ -94,7 +95,7 @@ Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、no 木桶短板理论告诉我们:一只木桶能装多少水,取决于最短的那块木板。同理,程序整体性能取决于最慢的操作(即 I/O 操作),所以单方面提高 CPU、内存的性能是无效的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201225170052.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201225170052.jpg) 为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都做出了贡献,主要体现为: @@ -192,11 +193,11 @@ while(server is active) { 在单核时代,所有的线程都是在一颗 CPU 上执行,CPU 缓存与内存的数据一致性容易解决。因为所有线程都是操作同一个 CPU 的缓存,一个线程对缓存的写,对另外一个线程来说一定是可见的。例如在下面的图中,线程 A 和线程 B 都是操作同一个 CPU 里面的缓存,所以线程 A 更新了变量 V 的值,那么线程 B 之后再访问变量 V,得到的一定是 V 的最新值(线程 A 写过的值)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701110313.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701110313.png) 多核时代,每颗 CPU 都有自己的缓存,这时 CPU 缓存与内存的数据一致性就没那么容易解决了,当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存。比如下图中,线程 A 操作的是 CPU-1 上的缓存,而线程 B 操作的是 CPU-2 上的缓存,很明显,这个时候线程 A 对变量 V 的操作对于线程 B 而言就不具备可见性了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701110431.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701110431.png) 【示例】线程不安全的示例 @@ -237,7 +238,7 @@ public class Test { 循环 10000 次 count+=1 操作如果改为循环 1 亿次,你会发现效果更明显,最终 count 的值接近 1 亿,而不是 2 亿。如果循环 10000 次,count 的值接近 20000,原因是两个线程不是同时启动的,有一个时差。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701110615.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701110615.png) ### 线程切换带来的原子性问题 @@ -257,7 +258,7 @@ Java 并发程序都是基于多线程的,自然也会涉及到任务切换, 操作系统做任务切换,可以发生在任何一条**CPU 指令**执行完,是的,是 CPU 指令,而不是高级语言里的一条语句。对于上面的三条指令来说,我们假设 count=0,如果线程 A 在指令 1 执行完后做线程切换,线程 A 和线程 B 按照下图的序列执行,那么我们会发现两个线程都执行了 count+=1 的操作,但是得到的结果不是我们期望的 2,而是 1。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701110946.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701110946.png) 我们潜意识里面觉得 count+=1 这个操作是一个不可分割的整体,就像一个原子一样,线程的切换可以发生在 count+=1 之前,也可以发生在 count+=1 之后,但就是不会发生在中间。**我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性**。CPU 能保证的原子操作是 CPU 指令级别的,而不是高级语言的操作符,这是违背我们直觉的地方。因此,很多时候我们需要在高级语言层面保证操作的原子性。 @@ -298,7 +299,7 @@ public class Singleton { 优化后会导致什么问题呢?我们假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 `instance != null` ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701111050.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701111050.png) ### 保证并发安全的思路 @@ -350,7 +351,7 @@ Java 中的 **无同步方案** 有: 死锁是当线程进入无限期等待状态时发生的情况,因为所请求的锁被另一个线程持有,而另一个线程又等待第一个线程持有的另一个锁。

- +

#### 避免死锁 @@ -389,7 +390,7 @@ Java 中的 **无同步方案** 有: 想象这样一个例子:两个人在狭窄的走廊里相遇,二者都很礼貌,试图移到旁边让对方先通过。但是他们最终在没有取得任何进展的情况下左右摇摆,因为他们都在同一时间向相同的方向移动。

- +

如图所示:两个线程想要通过一个 Worker 对象访问共享公共资源的情况,但是当他们看到另一个 Worker(在另一个线程上调用)也是“活动的”时,它们会尝试将该资源交给其他工作者并等待为它完成。如果最初我们让两名工作人员都活跃起来,他们将会面临活锁问题。 @@ -407,7 +408,7 @@ Java 中的 **无同步方案** 有: - 线程在等待一个本身(在其上调用 wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒。

- +

饥饿问题最经典的例子就是哲学家问题。如图所示:有五个哲学家用餐,每个人要获得两把叉子才可以就餐。当 2、4 就餐时,1、3、5 永远无法就餐,只能看着盘中的美食饥饿的等待着。 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/02.Java\347\272\277\347\250\213\345\237\272\347\241\200.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/02.Java\347\272\277\347\250\213\345\237\272\347\241\200.md" index 94394acdee..dd06476e3e 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/02.Java\347\272\277\347\250\213\345\237\272\347\241\200.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/02.Java\347\272\277\347\250\213\345\237\272\347\241\200.md" @@ -1,6 +1,7 @@ --- title: Java线程基础 date: 2019-12-24 23:52:25 +order: 02 categories: - Java - JavaSE @@ -701,7 +702,7 @@ public class Piped { ## 线程生命周期 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210102103928.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210102103928.png) `java.lang.Thread.State` 中定义了 **6** 种不同的线程状态,在给定的一个时刻,线程只能处于其中的一个状态。 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/03.Java\345\271\266\345\217\221\346\240\270\345\277\203\346\234\272\345\210\266.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/03.Java\345\271\266\345\217\221\346\240\270\345\277\203\346\234\272\345\210\266.md" index 01288144e0..34b9cad8f7 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/03.Java\345\271\266\345\217\221\346\240\270\345\277\203\346\234\272\345\210\266.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/03.Java\345\271\266\345\217\221\346\240\270\345\277\203\346\234\272\345\210\266.md" @@ -1,6 +1,7 @@ --- title: Java并发核心机制 date: 2019-12-25 22:19:09 +order: 03 categories: - Java - JavaSE @@ -29,7 +30,7 @@ Java 的 `java.util.concurrent` 包(简称 J.U.C)中提供了大量并发工 我个人理解,Java 并发框架可以分为以下层次。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/java-concurrent-basic-mechanism.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/java-concurrent-basic-mechanism.png) 由 Java 并发框架图不难看出,J.U.C 包中的工具类是基于 `synchronized`、`volatile`、`CAS`、`ThreadLocal` 这样的并发核心机制打造的。所以,要想深入理解 J.U.C 工具类的特性、为什么具有这样那样的特性,就必须先理解这些核心机制。 @@ -154,7 +155,7 @@ class Account { 问题就出在 this 这把锁上,this 这把锁可以保护自己的余额 this.balance,却保护不了别人的余额 target.balance,就像你不能用自家的锁来保护别人家的资产,也不能用自己的票来保护别人的座位一样。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701135257.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701135257.png) 应该保证使用的**锁能覆盖所有受保护资源**。 @@ -397,7 +398,7 @@ public void foo(Object lock) { Mark Word 记录了对象和锁有关的信息。Mark Word 在 64 位 JVM 中的长度是 64bit,我们可以一起看下 64 位 JVM 的存储结构是怎么样的。如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200629191250.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200629191250.png) 锁升级功能主要依赖于 Mark Word 中的锁标志位和是否偏向锁标志位,`synchronized` 同步锁就是从偏向锁开始的,随着竞争越来越激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。 @@ -418,7 +419,7 @@ Java 1.6 引入了偏向锁和轻量级锁,从而让 `synchronized` 拥有了 偏向锁的思想是偏向于**第一个获取锁对象的线程,这个线程在之后获取该锁就不再需要进行同步操作,甚至连 CAS 操作也不再需要**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200604105151.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200604105151.png) #### 轻量级锁 @@ -426,7 +427,7 @@ Java 1.6 引入了偏向锁和轻量级锁,从而让 `synchronized` 拥有了 当尝试获取一个锁对象时,如果锁对象标记为 `0|01`,说明锁对象的锁未锁定(unlocked)状态。此时虚拟机在当前线程的虚拟机栈中创建 Lock Record,然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00,表示该对象处于轻量级锁状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200604105248.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200604105248.png) #### 锁消除 / 锁粗化 @@ -1171,12 +1172,10 @@ server.tomcat.max-threads=1 当访问 id = 1 时,符合预期 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200731111854.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200731111854.png) 当访问 id = 2 时,before 的应答不是 null,而是 1,不符合预期。 -![image-20200731111921591](C:\Users\zp\AppData\Roaming\Typora\typora-user-images\image-20200731111921591.png) - 【分析】实际情况和预期存在偏差。Spring Boot 程序运行在 Tomcat 中,执行程序的线程是 Tomcat 的工作线程,而 Tomcat 的工作线程是基于线程池的。**线程池会重用固定的几个线程,一旦线程重用,那么很可能首次从** **ThreadLocal 获取的值是之前其他用户的请求遗留的值。这时,ThreadLocal 中的用户信息就是其他用户的信息**。 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/04.Java\351\224\201.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/04.Java\351\224\201.md" index a3dd7db071..d2a439d28f 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/04.Java\351\224\201.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/04.Java\351\224\201.md" @@ -1,6 +1,7 @@ --- title: Java锁 date: 2019-12-26 23:11:52 +order: 04 categories: - Java - JavaSE @@ -166,7 +167,7 @@ Java 1.5 之前,协调对共享对象的访问时可以使用的机制只有 ` Java 1.5 之后,增加了新的机制:`ReentrantLock`、`ReentrantReadWriteLock` ,这类锁的申请和释放都可以由程序所控制,所以常被称为显示锁。 -> 💡 `synchronized` 的用法和原理可以参考:[Java 并发基础机制 - synchronized](https://dunwu.github.io/blog/pages/2c6488/#%E4%BA%8Csynchronized) 。 +> 💡 `synchronized` 的用法和原理可以参考:[Java 并发基础机制 - synchronized](https://dunwu.github.io/waterdrop/pages/2c6488/#%E4%BA%8Csynchronized) 。 > > :bell: 注意:如果不需要 `ReentrantLock`、`ReentrantReadWriteLock` 所提供的高级同步特性,**应该优先考虑使用 `synchronized`** 。理由如下: > @@ -1151,7 +1152,7 @@ public abstract class AbstractQueuedSynchronizer - 这个整数状态的意义由子类来赋予,如`ReentrantLock` 中该状态值表示所有者线程已经重复获取该锁的次数,`Semaphore` 中该状态值表示剩余的许可数量。 - `head` 和 `tail` - AQS **维护了一个 `Node` 类型(AQS 的内部类)的双链表来完成同步状态的管理**。这个双链表是一个双向的 FIFO 队列,通过 `head` 和 `tail` 指针进行访问。当 **有线程获取锁失败后,就被添加到队列末尾**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/aqs_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/aqs_1.png) 再来看一下 `Node` 的源码 @@ -1200,13 +1201,13 @@ AQS 中使用 `acquire(int arg)` 方法获取独占锁,其大致流程如下 2. 如果获取同步状态不成功,AQS 会不断尝试利用 CAS 操作将当前线程插入等待同步队列的队尾,直到成功为止。 3. 接着,不断尝试为等待队列中的线程节点获取独占锁。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/aqs_2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/aqs_2.png) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/aqs_3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/aqs_3.png) 详细流程可以用下图来表示,请结合源码来理解(一图胜千言): -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/aqs_4.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/aqs_4.png) ##### 释放独占锁 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/05.Java\345\216\237\345\255\220\347\261\273.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/05.Java\345\216\237\345\255\220\347\261\273.md" index 8d069e847f..3c56191adf 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/05.Java\345\216\237\345\255\220\347\261\273.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/05.Java\345\216\237\345\255\220\347\261\273.md" @@ -1,6 +1,7 @@ --- title: Java原子类 date: 2019-12-26 23:11:52 +order: 05 categories: - Java - JavaSE @@ -52,7 +53,7 @@ permalink: /pages/25f78a/ - `AtomicLongFieldUpdater` - 长整型字段的原子更新器。 - `AtomicReferenceFieldUpdater` - 原子更新引用类型里的字段。 -> 这里不对 CAS、volatile、互斥同步做深入探讨。如果想了解更多细节,不妨参考:[Java 并发核心机制](https://dunwu.github.io/blog/pages/2c6488/) +> 这里不对 CAS、volatile、互斥同步做深入探讨。如果想了解更多细节,不妨参考:[Java 并发核心机制](https://dunwu.github.io/waterdrop/pages/2c6488/) ## 基本类型 @@ -130,7 +131,7 @@ private volatile int value; ## 引用类型 -Java 数据类型分为 **基本数据类型** 和 **引用数据类型** 两大类(不了解 Java 数据类型划分可以参考: [Java 基本数据类型](https://dunwu.github.io/blog/pages/55d693/) )。 +Java 数据类型分为 **基本数据类型** 和 **引用数据类型** 两大类(不了解 Java 数据类型划分可以参考: [Java 基本数据类型](https://dunwu.github.io/waterdrop/pages/55d693/) )。 上一节中提到了针对基本数据类型的原子类,那么如果想针对引用类型做原子操作怎么办?Java 也提供了相关的原子类: @@ -207,7 +208,7 @@ public class AtomicReferenceDemo2 { } ``` -原子类的实现基于 CAS 机制,而 CAS 存在 ABA 问题(不了解 ABA 问题,可以参考:[Java 并发基础机制 - CAS 的问题](https://dunwu.github.io/blog/pages/2c6488/#cas-%E7%9A%84%E9%97%AE%E9%A2%98))。正是为了解决 ABA 问题,才有了 `AtomicMarkableReference` 和 `AtomicStampedReference`。 +原子类的实现基于 CAS 机制,而 CAS 存在 ABA 问题(不了解 ABA 问题,可以参考:[Java 并发基础机制 - CAS 的问题](https://dunwu.github.io/waterdrop/pages/2c6488/#cas-%E7%9A%84%E9%97%AE%E9%A2%98))。正是为了解决 ABA 问题,才有了 `AtomicMarkableReference` 和 `AtomicStampedReference`。 `AtomicMarkableReference` 使用一个布尔值作为标记,修改时在 true / false 之间切换。这种策略不能根本上解决 ABA 问题,但是可以降低 ABA 发生的几率。常用于缓存或者状态描述这样的场景。 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/06.Java\345\271\266\345\217\221\345\222\214\345\256\271\345\231\250.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/06.Java\345\271\266\345\217\221\345\222\214\345\256\271\345\231\250.md" index 4f0fa450cf..a560174c41 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/06.Java\345\271\266\345\217\221\345\222\214\345\256\271\345\231\250.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/06.Java\345\271\266\345\217\221\345\222\214\345\256\271\345\231\250.md" @@ -1,6 +1,7 @@ --- title: Java并发和容器 date: 2020-02-02 17:54:36 +order: 06 categories: - Java - JavaSE @@ -31,7 +32,7 @@ permalink: /pages/b067d6/ 同步容器的同步原理就是在其 `get`、`set`、`size` 等主要方法上用 `synchronized` 修饰。 **`synchronized` 可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块**。 -> 想详细了解 `synchronized` 用法和原理可以参考:[Java 并发核心机制](https://dunwu.github.io/blog/pages/2c6488/) +> 想详细了解 `synchronized` 用法和原理可以参考:[Java 并发核心机制](https://dunwu.github.io/waterdrop/pages/2c6488/) #### 性能问题 @@ -121,7 +122,7 @@ for(int i=0;i= corePoolSize && workerCount < maximumPoolSize`,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务; 4. 如果`workerCount >= maximumPoolSize`,并且线程池内的阻塞队列已满,则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/java-thread-pool_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/java-thread-pool_1.png) ### 其他重要方法 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/08.Java\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/08.Java\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273.md" index f791251fd8..ddc3c82693 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/08.Java\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/08.Java\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273.md" @@ -1,6 +1,7 @@ --- title: Java并发工具类 date: 2019-12-24 23:52:25 +order: 08 categories: - Java - JavaSE @@ -22,7 +23,7 @@ permalink: /pages/02d274/ > > `CountDownLatch` 维护一个计数器 count,表示需要等待的事件数量。`countDown` 方法递减计数器,表示有一个事件已经发生。调用 `await` 方法的线程会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/CountDownLatch.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/CountDownLatch.png) `CountDownLatch` 是基于 AQS(`AbstractQueuedSynchronizer`) 实现的。 @@ -107,7 +108,7 @@ public class CountDownLatchDemo { `CyclicBarrier` 应用场景:`CyclicBarrier` 在并行迭代算法中非常有用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/CyclicBarrier.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/CyclicBarrier.png) `CyclicBarrier` 提供了 2 个构造方法 @@ -197,7 +198,7 @@ public class CyclicBarrierDemo { - `Semaphore` 可以用于实现资源池,如数据库连接池。 - `Semaphore` 可以用于将任何一种容器变成有界阻塞容器。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/Semaphore.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/Semaphore.png) `Semaphore` 提供了 2 个构造方法: diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/09.Java\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/09.Java\345\206\205\345\255\230\346\250\241\345\236\213.md" index 81891f1a67..780d3af230 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/09.Java\345\206\205\345\255\230\346\250\241\345\236\213.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/09.Java\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -1,6 +1,7 @@ --- title: Java内存模型 date: 2020-12-25 18:43:11 +order: 09 categories: - Java - JavaSE @@ -37,20 +38,20 @@ permalink: /pages/d4e06f/ 为了解决缓存一致性问题,**需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210102230327.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210102230327.png) ### 代码乱序执行优化 **除了高速缓存以外,为了使得处理器内部的运算单元尽量被充分利用**,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化。处理器会在计算之后将乱序执行的结果重组,**保证该结果与顺序执行的结果是一致的**,但不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210102223609.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210102223609.png) 乱序执行技术是处理器为提高运算速度而做出违背代码原有顺序的优化。 - **单核**环境下,处理器保证做出的优化不会导致执行结果远离预期目标,但在多核环境下却并非如此。 - **多核**环境下, 如果存在一个核的计算任务依赖另一个核的计算任务的中间结果,而且对相关数据读写没做任何防护措施,那么其顺序性并不能靠代码的先后顺序来保证。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210102224144.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210102224144.png) ## Java 内存模型 @@ -58,7 +59,7 @@ permalink: /pages/d4e06f/ JVM 中试图定义一种 Java 内存模型(Java Memory Model, JMM)来**屏蔽各种硬件和操作系统的内存访问差异**,以实现让 Java 程序 **在各种平台下都能达到一致的内存访问效果**。 -在 [Java 并发简介](https://dunwu.github.io/blog/pages/f6b642/) 中已经介绍了,并发安全需要满足可见性、有序性、原子性。其中,导致可见性的原因是缓存,导致有序性的原因是编译优化。那解决可见性、有序性最直接的办法就是**禁用缓存和编译优化** 。但这么做,性能就堪忧了。 +在 [Java 并发简介](https://dunwu.github.io/waterdrop/pages/f6b642/) 中已经介绍了,并发安全需要满足可见性、有序性、原子性。其中,导致可见性的原因是缓存,导致有序性的原因是编译优化。那解决可见性、有序性最直接的办法就是**禁用缓存和编译优化** 。但这么做,性能就堪忧了。 合理的方案应该是**按需禁用缓存以及编译优化**。那么,如何做到呢?,Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括 **volatile**、**synchronized** 和 **final** 三个关键字,以及 **Happens-Before 规则**。 @@ -70,11 +71,11 @@ JMM 规定了**所有的变量都存储在主内存(Main Memory)中**。 每条线程还有自己的工作内存(Working Memory),**工作内存中保留了该线程使用到的变量的主内存的副本**。工作内存是 JMM 的一个抽象概念,并不真实存在,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210102225839.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210102225839.png) 线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程间也无法直接访问对方工作内存中的变量,**线程间变量值的传递均需要通过主内存来完成**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210102225657.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210102225657.png) > 说明: > @@ -116,7 +117,7 @@ JMM 还规定了上述 8 种基本操作,需要满足以下规则: - 如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作,也不允许去 unlock 一个被其他线程锁定的变量。 - 对一个变量执行 unlock 操作之前,必须先把此变量同步到主内存中(执行 store 和 write 操作) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210102230708.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210102230708.png) ### 并发安全特性 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/10.ForkJoin\346\241\206\346\236\266.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/10.ForkJoin\346\241\206\346\236\266.md" index 9667a59581..6acee957da 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/10.ForkJoin\346\241\206\346\236\266.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/10.ForkJoin\346\241\206\346\236\266.md" @@ -1,6 +1,7 @@ --- title: ForkJoin框架 date: 2020-07-14 15:27:46 +order: 10 categories: - Java - JavaSE @@ -150,7 +151,7 @@ ForkJoinPool 本质上也是一个生产者 - 消费者的实现,但是更加 ForkJoinPool 中的任务队列采用的是双端队列,工作线程正常获取任务和“窃取任务”分别是从任务队列不同的端消费,这样能避免很多不必要的数据竞争。我们这里介绍的仅仅是简化后的原理,ForkJoinPool 的实现远比我们这里介绍的复杂,如果你感兴趣,建议去看它的源码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200703141326.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200703141326.png) ## 参考资料 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/11.Synchronized.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/11.Synchronized.md" index 928da10aa9..66b70287d8 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/11.Synchronized.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/11.Synchronized.md" @@ -1,6 +1,7 @@ --- title: Synchronized date: 2020-12-25 18:43:11 +order: 11 categories: - Java - JavaSE @@ -135,7 +136,7 @@ class Account { 问题就出在 this 这把锁上,this 这把锁可以保护自己的余额 this.balance,却保护不了别人的余额 target.balance,就像你不能用自家的锁来保护别人家的资产,也不能用自己的票来保护别人的座位一样。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701135257.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701135257.png) 应该保证使用的**锁能覆盖所有受保护资源**。 @@ -340,7 +341,7 @@ public void foo(Object lock) { Mark Word 记录了对象和锁有关的信息。Mark Word 在 64 位 JVM 中的长度是 64bit,我们可以一起看下 64 位 JVM 的存储结构是怎么样的。如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200629191250.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200629191250.png) 锁升级功能主要依赖于 Mark Word 中的锁标志位和释放偏向锁标志位,`synchronized` 同步锁就是从偏向锁开始的,随着竞争越来越激烈,偏向锁升级到轻量级锁,最终升级到重量级锁。 @@ -361,7 +362,7 @@ Java 1.6 引入了偏向锁和轻量级锁,从而让 `synchronized` 拥有了 偏向锁的思想是偏向于**第一个获取锁对象的线程,这个线程在之后获取该锁就不再需要进行同步操作,甚至连 CAS 操作也不再需要**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200604105151.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200604105151.png) ### 轻量级锁 @@ -369,7 +370,7 @@ Java 1.6 引入了偏向锁和轻量级锁,从而让 `synchronized` 拥有了 当尝试获取一个锁对象时,如果锁对象标记为 `0|01`,说明锁对象的锁未锁定(unlocked)状态。此时虚拟机在当前线程的虚拟机栈中创建 Lock Record,然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00,表示该对象处于轻量级锁状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200604105248.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200604105248.png) ### 锁消除 / 锁粗化 diff --git "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/README.md" "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/README.md" index 55c6719aa4..00ad309ecc 100644 --- "a/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/README.md" +++ "b/source/_posts/01.Java/01.JavaSE/05.\345\271\266\345\217\221/README.md" @@ -11,6 +11,7 @@ tags: - 并发 permalink: /pages/6e5393/ hidden: true +index: false --- # Java 并发 @@ -25,15 +26,15 @@ hidden: true > **关键词:`进程`、`线程`、`安全性`、`活跃性`、`性能`、`死锁`、`饥饿`、`上下文切换`** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200701113445.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200701113445.png) ### [Java 线程基础](02.Java线程基础.md) > **关键词:`Thread`、`Runnable`、`Callable`、`Future`、`wait`、`notify`、`notifyAll`、`join`、`sleep`、`yeild`、`线程状态`、`线程通信`** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630221707.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630221707.png) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/java-thread_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/java-thread_1.png) ### [Java 并发核心机制](03.Java并发核心机制.md) @@ -78,4 +79,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/01.JVM\344\275\223\347\263\273\347\273\223\346\236\204.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/01.JVM\344\275\223\347\263\273\347\273\223\346\236\204.md" index 7259219987..b773a32098 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/01.JVM\344\275\223\347\263\273\347\273\223\346\236\204.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/01.JVM\344\275\223\347\263\273\347\273\223\346\236\204.md" @@ -1,6 +1,7 @@ --- title: JVM 体系结构 date: 2021-05-24 15:41:47 +order: 01 categories: - Java - JavaSE @@ -47,7 +48,7 @@ Java 虚拟机的主要组件,包括**类加载器**、**运行时数据区** Hotspot 虚拟机拥有一个架构,它支持强大特性和能力的基础平台,支持实现高性能和强大的可伸缩性的能力。举个例子,Hotspot 虚拟机 JIT 编译器生成动态的优化,换句话说,它们在 Java 应用执行期做出优化,为底层系统架构生成高性能的本地机器指令。另外,经过它的运行时环境和多线程垃圾回收成熟的进化和连续的设计, Hotspot 虚拟机在高可用计算系统上产出了高伸缩性。
- +
### Hotspot 关键组件 @@ -55,7 +56,7 @@ Hotspot 虚拟机拥有一个架构,它支持强大特性和能力的基础平 Java 虚拟机有三个组件关注着什么时候进行性能优化,堆空间是对象所存储的地方,这个区域被启动时选择的垃圾回收器管理,大部分调优选项与调整堆大小和根据你的情况选择最适当的垃圾收集器相关。即时编译器对性能也有很大的影响,但是使用新版本的 Java 虚拟机时很少需要调整。
- +
### 性能指标 diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/02.JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/02.JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" index 6723559dc3..925f65800c 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/02.JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/02.JVM\345\206\205\345\255\230\345\214\272\345\237\237.md" @@ -1,6 +1,7 @@ --- title: Java 内存管理 date: 2020-06-28 16:19:00 +order: 02 categories: - Java - JavaSE @@ -40,7 +41,7 @@ Java 启动后,作为一个进程运行在操作系统中。 JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/jvm/jvm-memory-runtime-data-area.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/jvm/jvm-memory-runtime-data-area.png) ### 程序计数器 @@ -59,7 +60,7 @@ JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干 每个 Java 方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储 **局部变量表**、**操作数栈**、**常量池引用** 等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/jvm/jvm-stack.png!w640) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/jvm/jvm-stack.png!w640) - **局部变量表** - 32 位变量槽,存放了编译期可知的各种基本数据类型、对象引用、`ReturnAddress` 类型。 - **操作数栈** - 基于栈的执行引擎,虚拟机把操作数栈作为它的工作区,大多数指令都要从这里弹出数据、执行运算,然后把结果压回操作数栈。 @@ -87,7 +88,7 @@ JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干 二者的区别在于:**虚拟机栈为 Java 方法服务;本地方法栈为 Native 方法服务**。本地方法并不是用 Java 实现的,而是由 C 语言实现的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/jvm/jvm-native-method-stack.gif!w640) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/jvm/jvm-native-method-stack.gif!w640) > 🔔 注意:本地方法栈也会抛出 `StackOverflowError` 异常和 `OutOfMemoryError` 异常。 @@ -108,7 +109,7 @@ Java 堆是垃圾收集的主要区域(因此也被叫做"GC 堆")。现代 当一个对象被创建时,它首先进入新生代,之后有可能被转移到老年代中。新生代存放着大量的生命很短的对象,因此新生代在三个区域中垃圾回收的频率最高。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/jvm/jvm-heap.gif!w640) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/jvm/jvm-heap.gif!w640) > 🔔 注意:Java 堆不需要连续内存,并且可以动态扩展其内存,扩展失败会抛出 `OutOfMemoryError` 异常。 > @@ -238,19 +239,19 @@ class Student{ (3)class 文件加载、验证、准备以及解析,其中准备阶段会为类的静态变量分配内存,初始化为系统的初始值(这部分我在第 21 讲还会详细介绍)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630094250.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630094250.png) (4)完成上一个步骤后,将会进行最后一个初始化阶段。在这个阶段中,JVM 首先会执行构造器 `` 方法,编译器会在 `.java` 文件被编译成 `.class` 文件时,收集所有类的初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起成为 `()` 方法。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630094329.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630094329.png) (5)执行方法。启动 main 线程,执行 main 方法,开始执行第一行代码。此时堆内存中会创建一个 student 对象,对象引用 student 就存放在栈中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630094651.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630094651.png) (6)此时再次创建一个 JVMCase 对象,调用 sayHello 非静态方法,sayHello 方法属于对象 JVMCase,此时 sayHello 方法入栈,并通过栈中的 student 引用调用堆中的 Student 对象;之后,调用静态方法 print,print 静态方法属于 JVMCase 类,是从静态方法中获取,之后放入到栈中,也是通过 student 引用调用堆中的 student 对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630094714.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630094714.png) ## OutOfMemoryError diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/03.JVM\345\236\203\345\234\276\346\224\266\351\233\206.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/03.JVM\345\236\203\345\234\276\346\224\266\351\233\206.md" index a9611a4455..c80de69800 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/03.JVM\345\236\203\345\234\276\346\224\266\351\233\206.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/03.JVM\345\236\203\345\234\276\346\224\266\351\233\206.md" @@ -1,6 +1,7 @@ --- title: JVM 垃圾收集 date: 2020-06-07 09:21:16 +order: 03 categories: - Java - JavaSE @@ -45,7 +46,7 @@ public class ReferenceCountingGC { 通过 **GC Roots** 作为起始点进行搜索,JVM 将能够到达到的对象视为**存活**,不可达的对象视为**死亡**。
- +

可达性分析算法

@@ -187,7 +188,7 @@ obj = null; ### 标记 - 清除(Mark-Sweep)
- +
将需要回收的对象进行标记,然后清理掉被标记的对象。 @@ -200,7 +201,7 @@ obj = null; ### 标记 - 整理(Mark-Compact)
- +
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 @@ -210,7 +211,7 @@ obj = null; ### 复制(Copying)
- +
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。 @@ -229,7 +230,7 @@ obj = null; - 老年代使用:**标记 - 清理** 或者 **标记 - 整理** 算法
- +
#### 新生代 @@ -268,7 +269,7 @@ Java 虚拟机会记录 `Survivor` 区中的对象一共被来回复制了几次 ## 垃圾收集器
- +
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 @@ -284,7 +285,7 @@ Java 虚拟机会记录 `Survivor` 区中的对象一共被来回复制了几次 **串行收集器采用单线程 stop-the-world 的方式进行收集**。当内存不足时,串行 GC 设置停顿标识,待所有线程都进入安全点(Safepoint)时,应用线程暂停,串行 GC 开始工作,**采用单线程方式回收空间并整理内存**。
- +

Serial / Serial Old 收集器运行示意图

@@ -329,7 +330,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下 **在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 收集器 + Parallel Old 收集器。**
- +

Parallel / Parallel Old 收集器运行示意图

@@ -376,7 +377,7 @@ CMS 收集器运行步骤如下: 在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
- +

CMS 收集器运行示意图

@@ -452,7 +453,7 @@ CMS 收集器具有以下缺点: ParNew 收集器其实是 Serial 收集器的多线程版本。
- +

ParNew 收集器运行示意图

@@ -477,7 +478,7 @@ G1 最大的特点是引入分区的思路,弱化了分代的概念,合理 G1 取消了永久代,并把年轻代和老年代划分成多个大小相等的独立区域(Region),年轻代和老年代不再物理隔离。G1 可以直接对年轻代和老年代一起回收。
- +
通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。 @@ -487,7 +488,7 @@ G1 取消了永久代,并把年轻代和老年代划分成多个大小相等 #### G1 回收机制
- +

G1 收集器运行示意图

diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\347\261\273\345\212\240\350\275\275.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\347\261\273\345\212\240\350\275\275.md" index 7c3e2bf1a9..c3a7ec809c 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\347\261\273\345\212\240\350\275\275.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/04.JVM\347\261\273\345\212\240\350\275\275.md" @@ -1,6 +1,7 @@ --- title: JVM 类加载 date: 2020-06-17 15:06:46 +order: 04 categories: - Java - JavaSE @@ -14,7 +15,7 @@ permalink: /pages/17aad9/ # JVM 类加载 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200617145849.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200617145849.png) ## 类加载机制 @@ -26,7 +27,7 @@ permalink: /pages/17aad9/ ## 类的生命周期 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200617115110.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200617115110.png) Java 类的完整生命周期包括以下几个阶段: @@ -245,7 +246,7 @@ JVM 加载 `class` 文件到内存有两种方式: ### 类加载器分类 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200617115936.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200617115936.png) #### Bootstrap ClassLoader @@ -357,7 +358,7 @@ null 下图展示的类加载器之间的层次关系,称为类加载器的**双亲委派模型(Parents Delegation Model)**。**该模型要求除了顶层的 Bootstrap ClassLoader 外,其余的类加载器都应有自己的父类加载器**。**这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现**。
- +
**(1)工作过程** diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\345\255\227\350\212\202\347\240\201.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\345\255\227\350\212\202\347\240\201.md" index 42d08919e5..0975dbb3df 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\345\255\227\350\212\202\347\240\201.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/05.JVM\345\255\227\350\212\202\347\240\201.md" @@ -1,6 +1,7 @@ --- title: Java 字节码 date: 2019-10-28 22:04:39 +order: 05 categories: - Java - JavaSE @@ -23,19 +24,19 @@ Java 字节码是Java虚拟机执行的一种指令格式。之所以被称之 Java 能做到 “**一次编译,到处运行**”,一是因为 JVM 针对各种操作系统、平台都进行了定制;二是因为无论在什么平台,都可以编译生成固定格式的Java 字节码文件(`.class`)。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230419203137.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230419203137.png) ### 字节码文件结构 一个 Java 类编译后生成的 .class 文件内容如下图所示,是一堆十六进制数。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230419141404.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230419141404.png) 图来自 [字节码增强技术探索](https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html) 字节码看似杂乱无序,实际上是由严格的格式要求组成的。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230419154033.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230419154033.png) #### 魔数 diff --git a/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md b/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md index a0cb367dcf..928b3d88aa 100644 --- a/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md +++ b/source/_posts/01.Java/01.JavaSE/06.JVM/08.JavaAgent.md @@ -1,6 +1,7 @@ --- title: JavaAgent date: 2022-04-08 17:29:48 +order: 08 categories: - Java - JavaSE @@ -370,4 +371,4 @@ public class APPMain { ## 参考资料 -- [Java Agent 探针技术](https://juejin.cn/post/7086026013498408973) +- [Java Agent 探针技术](https://juejin.cn/post/7086026013498408973) \ No newline at end of file diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/11.JVM\345\221\275\344\273\244\350\241\214\345\267\245\345\205\267.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/11.JVM\345\221\275\344\273\244\350\241\214\345\267\245\345\205\267.md" index db7b3990f8..9b826f518a 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/11.JVM\345\221\275\344\273\244\350\241\214\345\267\245\345\205\267.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/11.JVM\345\221\275\344\273\244\350\241\214\345\267\245\345\205\267.md" @@ -1,6 +1,7 @@ --- title: JVM 命令行工具 date: 2020-07-30 17:56:33 +order: 11 categories: - Java - JavaSE @@ -339,7 +340,7 @@ jstack [option] pid ### thread dump 文件 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200730112431.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200730112431.png) 一个 Thread Dump 文件大致可以分为五个部分。 diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/12.JVM_GUI\345\267\245\345\205\267.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/12.JVM_GUI\345\267\245\345\205\267.md" index e50caa1958..359aa80a80 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/12.JVM_GUI\345\267\245\345\205\267.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/12.JVM_GUI\345\267\245\345\205\267.md" @@ -1,6 +1,7 @@ --- title: JVM GUI 工具 date: 2020-07-30 17:56:33 +order: 12 categories: - Java - JavaSE @@ -63,7 +64,7 @@ Java 应用开启 JMX 后,可以使用 `jconsole` 或 `jvisualvm` 进行监控 - `VM 摘要` - 显示有关 Java VM 的信息。 - `MBean` - 显示有关 MBean 的信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200730151422.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200730151422.png) ## jvisualvm @@ -75,30 +76,28 @@ Java 应用开启 JMX 后,可以使用 `jconsole` 或 `jvisualvm` 进行监控 jvisualvm 概述页面可以查看当前 Java 进程的基本信息,如:JDK 版本、Java 进程、JVM 参数等。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200730150147.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200730150147.png) ### jvisualvm 监控页面 在 jvisualvm 监控页面,可以看到 Java 进程的 CPU、内存、类加载、线程的实时变化。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200730150254.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200730150254.png) ### jvisualvm 线程页面 jvisualvm 线程页面展示了当前的线程状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200730150416.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200730150416.png) jvisualvm 还可以生成线程 Dump 文件,帮助进一步分析线程栈信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200730150830.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200730150830.png) ### jvisualvm 抽样器页面 jvisualvm 可以对 CPU、内存进行抽样,帮助我们进行性能分析。 -![image-20200730150648010](C:\Users\zp\AppData\Roaming\Typora\typora-user-images\image-20200730150648010.png) - ## MAT [MAT](https://www.eclipse.org/mat/) 即 Eclipse Memory Analyzer Tool 的缩写。 @@ -127,13 +126,13 @@ Failed to create the Java Virtual Machine ### MAT 分析 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200308092746.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200308092746.png) 点击 Leak Suspects 可以进入内存泄漏页面。 (1)首先,可以查看饼图了解内存的整体消耗情况 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200308150556.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200308150556.png) (2)缩小范围,寻找问题疑似点 @@ -152,15 +151,13 @@ MAT 同时打开两个堆转储文件,分别打开 Histogram,如下图。在 由于它是收费的,所以我本人使用较少。但是,它确实功能强大,且方便使用,还可以和 Intellij Idea 集成。 -![image-20200730152158398](C:\Users\zp\AppData\Roaming\Typora\typora-user-images\image-20200730152158398.png) - ## Arthas [Arthas](https://github.com/alibaba/arthas) 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。在线排查问题,无需重启;动态跟踪 Java 代码;实时监控 JVM 状态。 Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200730145030.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200730145030.png) ### Arthas 基础命令 diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/21.JVM\345\256\236\346\210\230.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/21.JVM\345\256\236\346\210\230.md" index 1f343cbb8e..a6a13ce5f2 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/21.JVM\345\256\236\346\210\230.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/21.JVM\345\256\236\346\210\230.md" @@ -1,6 +1,7 @@ --- title: JVM 实战 date: 2019-10-28 22:04:39 +order: 21 categories: - Java - JavaSE @@ -203,11 +204,11 @@ Full GC 回收日志: YOUNG GC -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220107093538.jfif) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220107093538.jfif) FULL GC -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220107093543.jfif) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220107093543.jfif) #### CPU 过高 @@ -434,5 +435,5 @@ address 即为远程 debug 的监听端口。 - [Java 服务 GC 参数调优案例](https://segmentfault.com/a/1190000005174819) - [JVM 调优总结(5):典型配置](http://www.importnew.com/19264.html) - [如何合理的规划一次 jvm 性能调优](https://juejin.im/post/59f02f406fb9a0451869f01c) -- [jvm 系列(九):如何优化 Java GC「译」](http://www.ityouknow.com/jvm/2017/09/21/How-to-optimize-Java-GC.html) -- https://my.oschina.net/feichexia/blog/196575 \ No newline at end of file +- [jvm 系列(九):如何优化 Java GC“译”](http://www.ityouknow.com/jvm/2017/09/21/How-to-optimize-Java-GC.html) +- https://my.oschina.net/feichexia/blog/196575 diff --git "a/source/_posts/01.Java/01.JavaSE/06.JVM/22.Java\346\225\205\351\232\234\350\257\212\346\226\255.md" "b/source/_posts/01.Java/01.JavaSE/06.JVM/22.Java\346\225\205\351\232\234\350\257\212\346\226\255.md" index 48246c1f25..f9499d4d0e 100644 --- "a/source/_posts/01.Java/01.JavaSE/06.JVM/22.Java\346\225\205\351\232\234\350\257\212\346\226\255.md" +++ "b/source/_posts/01.Java/01.JavaSE/06.JVM/22.Java\346\225\205\351\232\234\350\257\212\346\226\255.md" @@ -1,6 +1,7 @@ --- title: Java 故障诊断 date: 2020-07-30 17:56:33 +order: 22 categories: - Java - JavaSE @@ -23,11 +24,11 @@ Java 应用出现线上故障,如何进行诊断? 一般来说,服务器故障诊断的整体思路如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200309181645.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200309181645.png) 应用故障诊断思路: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200309181831.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200309181831.png) ## CPU 问题 @@ -317,7 +318,7 @@ ls -l /proc/pid/task | wc -l 查看 GC 日志,如果有明显提示 OOM 问题,那就可以根据提示信息,较为快速的定位问题。 -> OOM 定位可以参考:[JVM 内存管理 之 OutOfMemoryError](jvm-memory.md#三OutOfMemoryError) +> OOM 定位可以参考:[JVM 内存区域之 OutOfMemoryError](02.JVM内存区域.md#OutOfMemoryError) ### Minor GC diff --git a/source/_posts/01.Java/01.JavaSE/06.JVM/README.md b/source/_posts/01.Java/01.JavaSE/06.JVM/README.md index 9ef35dea17..df64d7f65d 100644 --- a/source/_posts/01.Java/01.JavaSE/06.JVM/README.md +++ b/source/_posts/01.Java/01.JavaSE/06.JVM/README.md @@ -11,6 +11,7 @@ tags: - JVM permalink: /pages/51172b/ hidden: true +index: false --- # JVM 教程 @@ -40,4 +41,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 🏠 [JAVACORE 首页](https://github.com/dunwu/javacore) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/01.JavaSE/99.Java\351\235\242\350\257\225.md" "b/source/_posts/01.Java/01.JavaSE/99.Java\351\235\242\350\257\225.md" index d478f57d29..1444453af5 100644 --- "a/source/_posts/01.Java/01.JavaSE/99.Java\351\235\242\350\257\225.md" +++ "b/source/_posts/01.Java/01.JavaSE/99.Java\351\235\242\350\257\225.md" @@ -1,6 +1,7 @@ --- title: Java 面试总结 date: 2020-06-04 13:51:00 +order: 99 categories: - Java - JavaSE @@ -228,7 +229,7 @@ LinkedList 是双链表,数据有序存储。 #### Java 线程生命周期中有哪些状态?各状态之间如何切换? -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/java-thread_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/java-thread_1.png) `java.lang.Thread.State` 中定义了 **6** 种不同的线程状态,在给定的一个时刻,线程只能处于其中的一个状态。 @@ -381,7 +382,7 @@ Java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并 ### 并发机制的底层实现 -> 👉 参考阅读:[Java 并发核心机制](https://dunwu.github.io/blog/pages/2c6488/) +> 👉 参考阅读:[Java 并发核心机制](https://dunwu.github.io/waterdrop/pages/2c6488/) #### ⭐⭐⭐ `synchronized` @@ -538,13 +539,13 @@ ThreadLocalMap 的 `Entry` 继承了 `WeakReference`,所以它的 key (`Thre - 对于一个对象的成员方法,这些方法中包含本地变量,仍需要存储在栈区,即使它们所属的对象在堆区。 - 对于一个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/java-memory-model_3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/java-memory-model_3.png) > 👉 参考阅读:[全面理解 Java 内存模型](https://blog.csdn.net/suifeng3051/article/details/52611310) ### 同步容器和并发容器 -> 👉 参考阅读:[Java 并发容器](https://dunwu.github.io/blog/pages/b067d6/) +> 👉 参考阅读:[Java 并发容器](https://dunwu.github.io/waterdrop/pages/b067d6/) #### ⭐ 同步容器 @@ -620,12 +621,12 @@ CopyOnWrite 字面意思为写入时复制。CopyOnWriteArrayList 是线程安 - 写时复制集合返回的迭代器不会抛出 ConcurrentModificationException,因为它们在数组的快照上工作,并且无论后续的修改(2,4)如何,都会像迭代器创建时那样完全返回元素。

- +

### 并发锁 -> 👉 参考阅读:[Java 并发锁](https://dunwu.github.io/blog/pages/e2e047/) +> 👉 参考阅读:[Java 并发锁](https://dunwu.github.io/waterdrop/pages/e2e047/) #### ⭐⭐ 锁类型 @@ -799,7 +800,7 @@ AQS 中使用 `releaseShared(int arg)` 方法释放共享锁。 ### 原子变量类 -> 👉 参考阅读:[Java 原子类](https://dunwu.github.io/blog/pages/25f78a/) +> 👉 参考阅读:[Java 原子类](https://dunwu.github.io/waterdrop/pages/25f78a/) #### ⭐ 原子类简介 @@ -843,7 +844,7 @@ AQS 中使用 `releaseShared(int arg)` 方法释放共享锁。 ### 并发工具类 -> 👉 参考阅读:[Java 并发工具类](https://dunwu.github.io/blog/pages/02d274/) +> 👉 参考阅读:[Java 并发工具类](https://dunwu.github.io/waterdrop/pages/02d274/) #### ⭐ CountDownLatch @@ -857,7 +858,7 @@ AQS 中使用 `releaseShared(int arg)` 方法释放共享锁。 `CountDownLatch` 维护一个计数器 count,表示需要等待的事件数量。`countDown` 方法递减计数器,表示有一个事件已经发生。调用 `await` 方法的线程会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/CountDownLatch.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/CountDownLatch.png) **原理** @@ -877,7 +878,7 @@ AQS 中使用 `releaseShared(int arg)` 方法释放共享锁。 `CyclicBarrier` 维护一个计数器 count。每次执行 `await` 方法之后,count 加 1,直到计数器的值和设置的值相等,等待的所有线程才会继续执行。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/CyclicBarrier.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/CyclicBarrier.png) **原理** @@ -900,11 +901,11 @@ AQS 中使用 `releaseShared(int arg)` 方法释放共享锁。 - `Semaphore` 可以用于实现资源池,如数据库连接池。 - `Semaphore` 可以用于将任何一种容器变成有界阻塞容器。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/Semaphore.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/Semaphore.png) ### 线程池 -> 👉 参考阅读:[Java 线程池](https://dunwu.github.io/blog/pages/ad9680/) +> 👉 参考阅读:[Java 线程池](https://dunwu.github.io/waterdrop/pages/ad9680/) #### ⭐⭐ ThreadPoolExecutor @@ -914,7 +915,7 @@ AQS 中使用 `releaseShared(int arg)` 方法释放共享锁。 **原理** -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javacore/concurrent/java-thread-pool_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javacore/concurrent/java-thread-pool_1.png) **参数** diff --git a/source/_posts/01.Java/01.JavaSE/README.md b/source/_posts/01.Java/01.JavaSE/README.md index 7d96578461..fb9048c57d 100644 --- a/source/_posts/01.Java/01.JavaSE/README.md +++ b/source/_posts/01.Java/01.JavaSE/README.md @@ -9,6 +9,7 @@ tags: - JavaSE permalink: /pages/69d2f8/ hidden: true +index: false --- # JavaSE @@ -43,7 +44,7 @@ hidden: true ### [Java 容器](03.容器) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221175550.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221175550.png) - [Java 容器简介](03.容器/01.Java容器简介.md) - 关键词:`Collection`、`泛型`、`Iterable`、`Iterator`、`Comparable`、`Comparator`、`Cloneable`、`fail-fast` - [Java 容器之 List](03.容器/02.Java容器之List.md) - 关键词:`List`、`ArrayList`、`LinkedList` @@ -54,7 +55,7 @@ hidden: true ### [Java IO](04.IO) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630205329.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630205329.png) - [Java IO 模型](04.IO/01.JavaIO模型.md) - 关键词:`InputStream`、`OutputStream`、`Reader`、`Writer`、`阻塞` - [Java NIO](04.IO/02.JavaNIO.md) - 关键词:`Channel`、`Buffer`、`Selector`、`非阻塞`、`多路复用` @@ -64,7 +65,7 @@ hidden: true ### [Java 并发](05.并发) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221175827.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221175827.png) - [Java 并发简介](05.并发/01.Java并发简介.md) - 关键词:`进程`、`线程`、`安全性`、`活跃性`、`性能`、`死锁`、`饥饿`、`上下文切换` - [Java 线程基础](05.并发/02.Java线程基础.md) - 关键词:`Thread`、`Runnable`、`Callable`、`Future`、`wait`、`notify`、`notifyAll`、`join`、`sleep`、`yeild`、`线程状态`、`线程通信` @@ -79,7 +80,7 @@ hidden: true ### [Java 虚拟机](06.JVM) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200628154803.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200628154803.png) - [JVM 体系结构](06.JVM/01.JVM体系结构.md) - [JVM 内存区域](06.JVM/02.JVM内存区域.md) - 关键词:`程序计数器`、`虚拟机栈`、`本地方法栈`、`堆`、`方法区`、`运行时常量池`、`直接内存`、`OutOfMemoryError`、`StackOverflowError` @@ -128,4 +129,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/01.JavaWeb\344\271\213Servlet\346\214\207\345\215\227.md" "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/01.JavaWeb\344\271\213Servlet\346\214\207\345\215\227.md" index 883a451679..2133814eb3 100644 --- "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/01.JavaWeb\344\271\213Servlet\346\214\207\345\215\227.md" +++ "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/01.JavaWeb\344\271\213Servlet\346\214\207\345\215\227.md" @@ -1,6 +1,7 @@ --- title: JavaWeb 之 Servlet 指南 date: 2020-08-24 19:41:46 +order: 01 categories: - Java - JavaEE diff --git "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/02.JavaWeb\344\271\213Jsp\346\214\207\345\215\227.md" "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/02.JavaWeb\344\271\213Jsp\346\214\207\345\215\227.md" index e7a8036f70..32b8ab7ce8 100644 --- "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/02.JavaWeb\344\271\213Jsp\346\214\207\345\215\227.md" +++ "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/02.JavaWeb\344\271\213Jsp\346\214\207\345\215\227.md" @@ -1,6 +1,7 @@ --- title: JavaWeb 之 Jsp 指南 date: 2020-02-07 23:04:47 +order: 02 categories: - Java - JavaEE @@ -20,7 +21,7 @@ permalink: /pages/8cc787/ `JSP`全称`Java Server Pages`,是一种动态网页开发技术。 -它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以`<%`开头以`%>`结束。 +它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以 `<%` 开头以 `%>` 结束。 JSP 是一种 Java servlet,主要用于实现 Java web 应用程序的用户界面部分。网页开发者们通过结合 HTML 代码、XHTML 代码、XML 元素以及嵌入 JSP 操作和命令来编写 JSP。 @@ -333,14 +334,14 @@ pageEncoding="UTF-8"%> 不同情况下使用注释的语法规则: -| **语法** | 描述 | -| ---------------- | ----------------------------------------------------- | -| `<%-- 注释 --%>` | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 | +| **语法** | 描述 | +| ---------------- |-------------------------------| +| `<%-- 注释 --%>` | JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 | | `` | HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 | -| `<%` | 代表静态 <%常量 | -| `%>` | 代表静态 %> 常量 | -| `'` | 在属性中使用的单引号 | -| `"` | 在属性中使用的双引号 | +| `<%` | 代表静态 `<%` 常量 | +| `%>` | 代表静态 `%>` 常量 | +| `'` | 在属性中使用的单引号 | +| `"` | 在属性中使用的双引号 | ### 控制语句 @@ -1353,7 +1354,7 @@ Apache Tomcat 安装 JSTL 库步骤如下: ``` -使用任何库,你必须在每个 JSP 文件中的头部包含 **** 标签。 +使用任何库,你必须在每个 JSP 文件中的头部包含 **``** 标签。 ### 核心标签 @@ -1363,22 +1364,22 @@ Apache Tomcat 安装 JSTL 库步骤如下: <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> ``` -| 标签 | 描述 | -| :---------------------------------------------------------------------- | :-------------------------------------------------------------------------- | -| [``](http://www.runoob.com/jsp/jstl-core-out-tag.html) | 用于在 JSP 中显示数据,就像<%= ... > | -| [``](http://www.runoob.com/jsp/jstl-core-set-tag.html) | 用于保存数据 | -| [``](http://www.runoob.com/jsp/jstl-core-remove-tag.html) | 用于删除数据 | -| [``](http://www.runoob.com/jsp/jstl-core-catch-tag.html) | 用来处理产生错误的异常状况,并且将错误信息储存起来 | -| [``](http://www.runoob.com/jsp/jstl-core-if-tag.html) | 与我们在一般程序中用的 if 一样 | -| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 本身只当做的父标签 | -| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 的子标签,用来判断条件是否成立 | -| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 的子标签,接在标签后,当标签判断为 false 时被执行 | -| [``](http://www.runoob.com/jsp/jstl-core-import-tag.html) | 检索一个绝对或相对 URL,然后将其内容暴露给页面 | -| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 基础迭代标签,接受多种集合类型 | -| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 根据指定的分隔符来分隔内容并迭代输出 | -| [``](http://www.runoob.com/jsp/jstl-core-param-tag.html) | 用来给包含或重定向的页面传递参数 | -| [``](http://www.runoob.com/jsp/jstl-core-redirect-tag.html) | 重定向至一个新的 URL. | -| [``](http://www.runoob.com/jsp/jstl-core-url-tag.html) | 使用可选的查询参数来创造一个 URL | +| 标签 | 描述 | +| :---------------------------------------------------------------------- |:------------------------------------------------------------------| +| [``](http://www.runoob.com/jsp/jstl-core-out-tag.html) | 用于在 JSP 中显示数据,就像<%= ... > | +| [``](http://www.runoob.com/jsp/jstl-core-set-tag.html) | 用于保存数据 | +| [``](http://www.runoob.com/jsp/jstl-core-remove-tag.html) | 用于删除数据 | +| [``](http://www.runoob.com/jsp/jstl-core-catch-tag.html) | 用来处理产生错误的异常状况,并且将错误信息储存起来 | +| [``](http://www.runoob.com/jsp/jstl-core-if-tag.html) | 与我们在一般程序中用的 if 一样 | +| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | 本身只当做 `` 和 `` 的父标签 | +| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | `` 的子标签,用来判断条件是否成立 | +| [``](http://www.runoob.com/jsp/jstl-core-choose-tag.html) | `` 的子标签,接在 `` 标签后,当 `` 标签判断为 false 时被执行 | +| [``](http://www.runoob.com/jsp/jstl-core-import-tag.html) | 检索一个绝对或相对 URL,然后将其内容暴露给页面 | +| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 基础迭代标签,接受多种集合类型 | +| [``](http://www.runoob.com/jsp/jstl-core-foreach-tag.html) | 根据指定的分隔符来分隔内容并迭代输出 | +| [``](http://www.runoob.com/jsp/jstl-core-param-tag.html) | 用来给包含或重定向的页面传递参数 | +| [``](http://www.runoob.com/jsp/jstl-core-redirect-tag.html) | 重定向至一个新的 URL. | +| [``](http://www.runoob.com/jsp/jstl-core-url-tag.html) | 使用可选的查询参数来创造一个 URL | ### 格式化标签 @@ -1437,18 +1438,18 @@ JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标 下载地址: -| 标签 | 描述 | -| :----------------------------------------------------------------------- | :---------------------------------------------------------- | -| [``](http://www.runoob.com/jsp/jstl-xml-out-tag.html) | 与<%= ... >,类似,不过只用于 XPath 表达式 | -| [``](http://www.runoob.com/jsp/jstl-xml-parse-tag.html) | 解析 XML 数据 | -| [``](http://www.runoob.com/jsp/jstl-xml-set-tag.html) | 设置 XPath 表达式 | -| [``](http://www.runoob.com/jsp/jstl-xml-if-tag.html) | 判断 XPath 表达式,若为真,则执行本体中的内容,否则跳过本体 | -| [``](http://www.runoob.com/jsp/jstl-xml-foreach-tag.html) | 迭代 XML 文档中的节点 | -| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的父标签 | -| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的子标签,用来进行条件判断 | -| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | 的子标签,当判断为 false 时被执行 | -| [``](http://www.runoob.com/jsp/jstl-xml-transform-tag.html) | 将 XSL 转换应用在 XML 文档中 | -| [``](http://www.runoob.com/jsp/jstl-xml-param-tag.html) | 与共同使用,用于设置 XSL 样式表 | +| 标签 | 描述 | +| :----------------------------------------------------------------------- |:----------------------------------------------| +| [``](http://www.runoob.com/jsp/jstl-xml-out-tag.html) | 与 `<%= ... >`,类似,不过只用于 XPath 表达式 | +| [``](http://www.runoob.com/jsp/jstl-xml-parse-tag.html) | 解析 XML 数据 | +| [``](http://www.runoob.com/jsp/jstl-xml-set-tag.html) | 设置 XPath 表达式 | +| [``](http://www.runoob.com/jsp/jstl-xml-if-tag.html) | 判断 XPath 表达式,若为真,则执行本体中的内容,否则跳过本体 | +| [``](http://www.runoob.com/jsp/jstl-xml-foreach-tag.html) | 迭代 XML 文档中的节点 | +| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | `` 和 `` 的父标签 | +| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | `` 的子标签,用来进行条件判断 | +| [``](http://www.runoob.com/jsp/jstl-xml-choose-tag.html) | `` 的子标签,当 `` 判断为 false 时被执行 | +| [``](http://www.runoob.com/jsp/jstl-xml-transform-tag.html) | 将 XSL 转换应用在 XML 文档中 | +| [``](http://www.runoob.com/jsp/jstl-xml-param-tag.html) | 与 `` 共同使用,用于设置 XSL 样式表 | ### JSTL 函数 @@ -1649,7 +1650,7 @@ public class HelloTag extends SimpleTagSupport { } ``` -属性的名称是"message",所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的元素添加此属性: +属性的名称是"message",所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的 `` 元素添加此属性: ``` diff --git "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/03.JavaWeb\344\271\213Filter\345\222\214Listener.md" "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/03.JavaWeb\344\271\213Filter\345\222\214Listener.md" index bebb096c00..aea602475f 100644 --- "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/03.JavaWeb\344\271\213Filter\345\222\214Listener.md" +++ "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/03.JavaWeb\344\271\213Filter\345\222\214Listener.md" @@ -1,6 +1,7 @@ --- title: JavaWeb 之 Filter 和 Listener date: 2020-08-24 19:41:46 +order: 03 categories: - Java - JavaEE @@ -23,7 +24,7 @@ permalink: /pages/82df5f/ Filter 提供了过滤链(Filter Chain)的概念,一个过滤链包括多个 Filter。客户端请求 request 在抵达 Servlet 之前会经过过滤链的所有 Filter,服务器响应 response 从 Servlet 抵达客户端浏览器之前也会经过过滤链的所有 FIlter。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559054413341.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559054413341.png) ### 过滤器方法 diff --git "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/04.JavaWeb\344\271\213Cookie\345\222\214Session.md" "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/04.JavaWeb\344\271\213Cookie\345\222\214Session.md" index 7903c50af9..3729f4a6b1 100644 --- "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/04.JavaWeb\344\271\213Cookie\345\222\214Session.md" +++ "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/04.JavaWeb\344\271\213Cookie\345\222\214Session.md" @@ -1,6 +1,7 @@ --- title: JavaWeb 之 Cookie 和 Session date: 2020-08-24 19:41:46 +order: 04 categories: - Java - JavaEE diff --git "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/99.JavaWeb\351\235\242\347\273\217.md" "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/99.JavaWeb\351\235\242\347\273\217.md" index c2b9e05208..8e803b40d4 100644 --- "a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/99.JavaWeb\351\235\242\347\273\217.md" +++ "b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/99.JavaWeb\351\235\242\347\273\217.md" @@ -1,6 +1,7 @@ --- title: JavaWeb 面经 date: 2020-02-07 23:04:47 +order: 99 categories: - Java - JavaEE diff --git a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/README.md b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/README.md index 311f3b3b75..56a9b42a58 100644 --- a/source/_posts/01.Java/02.JavaEE/01.JavaWeb/README.md +++ b/source/_posts/01.Java/02.JavaEE/01.JavaWeb/README.md @@ -9,6 +9,7 @@ tags: - JavaWeb permalink: /pages/50f49f/ hidden: true +index: false --- # ☕ JavaWeb diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/01.Tomcat\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/01.Tomcat\345\277\253\351\200\237\345\205\245\351\227\250.md" index 70798d7045..05822b5e40 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/01.Tomcat\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/01.Tomcat\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Tomcat 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - JavaEE @@ -111,7 +112,7 @@ tar -zxf apache-tomcat-8.5.24.tar.gz 启动后,访问 `http://localhost:8080` ,可以看到 Tomcat 安装成功的测试页面。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/tomcat.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/tomcat.png) ### 2.2. 配置 @@ -364,7 +365,7 @@ public class SimpleTomcatServer { - 设置启动应用的端口、JVM 参数、启动浏览器等。 - 成功后,可以访问 `http://localhost:8080/`(当然,你也可以在 url 中设置上下文名称)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/tomcat-intellij-run-config.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/tomcat-intellij-run-config.png) > **说明** > @@ -374,7 +375,7 @@ public class SimpleTomcatServer { ## 3. Tomcat 架构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201113193431.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201113193431.png) Tomcat 要实现 2 个核心功能: @@ -402,7 +403,7 @@ Tomcat 支持的应用层协议有: Tomcat 支持多种 I/O 模型和应用层协议。为了实现这点,一个容器可能对接多个连接器。但是,单独的连接器或容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作 Service 组件。Tomcat 内可能有多个 Service,通过在 Tomcat 中配置多个 Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201111093124.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201111093124.png) **一个 Tomcat 实例有一个或多个 Service;一个 Service 有多个 Connector 和 Container**。Connector 和 Container 之间通过标准的 ServletRequest 和 ServletResponse 通信。 @@ -418,13 +419,13 @@ Tomcat 支持多种 I/O 模型和应用层协议。为了实现这点,一个 Tomcat 设计了 3 个组件来实现这 3 个功能,分别是 **`EndPoint`**、**`Processor`** 和 **`Adapter`**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201111101440.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201111101440.png) 组件间通过抽象接口交互。这样做还有一个好处是**封装变化。**这是面向对象设计的精髓,将系统中经常变化的部分和稳定的部分隔离,有助于增加复用性,并降低系统耦合度。网络通信的 I/O 模型是变化的,可能是非阻塞 I/O、异步 I/O 或者 APR。应用层协议也是变化的,可能是 HTTP、HTTPS、AJP。浏览器端发送的请求信息也是变化的。但是整体的处理逻辑是不变的,EndPoint 负责提供字节流给 Processor,Processor 负责提供 Tomcat Request 对象给 Adapter,Adapter 负责提供 ServletRequest 对象给容器。 如果要支持新的 I/O 方案、新的应用层协议,只需要实现相关的具体子类,上层通用的处理逻辑是不变的。由于 I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO2 + AJP。Tomcat 的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫 ProtocolHandler 的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol 和 AjpNioProtocol。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201027091819.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201027091819.png) #### 3.2.1. ProtocolHandler 组件 @@ -444,7 +445,7 @@ EndPoint 是一个接口,对应的抽象实现类是 AbstractEndpoint,而 Ab Processor 是一个接口,定义了请求的处理等方法。它的抽象实现类 AbstractProcessor 对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有 AJPProcessor、HTTP11Processor 等,这些具体实现类实现了特定协议的解析方法和请求处理方式。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201113185929.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201113185929.png) 从图中我们看到,EndPoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 Run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用 Adapter 的 Service 方法。 @@ -471,7 +472,7 @@ Tomcat 是怎么确定请求是由哪个 Wrapper 容器里的 Servlet 来处理 举例来说,假如有一个网购系统,有面向网站管理人员的后台管理系统,还有面向终端客户的在线购物系统。这两个系统跑在同一个 Tomcat 上,为了隔离它们的访问域名,配置了两个虚拟域名:`manage.shopping.com`和`user.shopping.com`,网站管理人员通过`manage.shopping.com`域名访问 Tomcat 去管理用户和商品,而用户管理和商品管理是两个单独的 Web 应用。终端客户通过`user.shopping.com`域名去搜索商品和下订单,搜索功能和订单管理也是两个独立的 Web 应用。如下所示,演示了 url 应声 Servlet 的处理流程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201113192022.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201113192022.jpg) 假如有用户访问一个 URL,比如图中的`http://user.shopping.com:8080/order/buy`,Tomcat 如何将这个 URL 定位到一个 Servlet 呢? @@ -490,7 +491,7 @@ Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理 先来了解一下 Valve 和 Pipeline 接口的设计: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/Pipeline与Valve.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/Pipeline与Valve.png) - 每一个容器都有一个 Pipeline 对象,只要触发这个 Pipeline 的第一个 Valve,这个容器里 Pipeline 中的 Valve 就都会被调用到。但是,不同容器的 Pipeline 是怎么链式触发的呢,比如 Engine 中 Pipeline 需要调用下层容器 Host 中的 Pipeline。 - 这是因为 Pipeline 中还有个 getBasic 方法。这个 BasicValve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。 @@ -499,7 +500,7 @@ Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理 - 各层容器对应的 basic valve 分别是 `StandardEngineValve`、`StandardHostValve`、 `StandardContextValve`、`StandardWrapperValve`。 - 由于 Valve 是一个处理点,因此 invoke 方法就是来处理请求的。注意到 Valve 中有 getNext 和 setNext 方法,因此我们大概可以猜到有一个链表将 Valve 链起来了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/请求处理过程.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/请求处理过程.png) 整个调用过程由连接器中的 Adapter 触发的,它会调用 Engine 的第一个 Valve: @@ -511,7 +512,7 @@ connector.getService().getContainer().getPipeline().getFirst().invoke(request, r ### 4.1. Tomcat 的启动过程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201118145455.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201118145455.png) 1. Tomcat 是一个 Java 程序,它的运行从执行 `startup.sh` 脚本开始。`startup.sh` 会启动一个 JVM 来运行 Tomcat 的启动类 `Bootstrap`。 2. `Bootstrap` 会初始化 Tomcat 的类加载器并实例化 `Catalina`。 @@ -731,12 +732,12 @@ ContextConfig 解析 web.xml 顺序: ### 4.3. LifeCycle -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201118105012.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201118105012.png) #### 4.3.1. 请求处理过程
- +
1. 根据 server.xml 配置的指定的 connector 以及端口监听 http、或者 ajp 请求 @@ -747,25 +748,25 @@ ContextConfig 解析 web.xml 顺序: ### 4.4. Connector 流程
- +
#### 4.4.1. 阻塞 IO
- +
#### 4.4.2. 非阻塞 IO
- +
#### 4.4.3. IO 多路复用
- +
阻塞与非阻塞的区别在于进行读操作和写操作的系统调用时,如果此时内核态没有数据可读或者没有缓冲空间可写时,是否阻塞。 @@ -775,7 +776,7 @@ IO 多路复用的好处在于可同时监听多个 socket 的可读和可写事 #### 4.4.4. Tomcat 各类 Connector 对比
- +
- JIO:用 java.io 编写的 TCP 模块,阻塞 IO @@ -796,7 +797,7 @@ Apache Portable Runtime 是一个高度可移植的库,它是 Apache HTTP Serv **NIO 处理相关类**
- +
Poller 线程从 EventQueue 获取 PollerEvent,并执行 PollerEvent 的 run 方法,调用 Selector 的 select 方法,如果有可读的 Socket 则创建 Http11NioProcessor,放入到线程池中执行; @@ -829,7 +830,7 @@ Note: ### 4.6. 异步 Servlet
- +
传统流程: @@ -839,7 +840,7 @@ Note: - 最后,根据处理的结果提交响应,Servlet 线程结束
- +
异步处理流程: diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/02.Tomcat\350\277\236\346\216\245\345\231\250.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/02.Tomcat\350\277\236\346\216\245\345\231\250.md" index 5c524f7e42..fbf9fe8e86 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/02.Tomcat\350\277\236\346\216\245\345\231\250.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/02.Tomcat\350\277\236\346\216\245\345\231\250.md" @@ -1,6 +1,7 @@ --- title: Tomcat连接器 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - JavaEE @@ -20,7 +21,7 @@ permalink: /pages/13f070/ Tomcat 的 NioEndPoint 组件利用 Java NIO 实现了 I/O 多路复用模型。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127094302.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127094302.jpg) NioEndPoint 子组件功能简介: @@ -123,7 +124,7 @@ private final SynchronizedQueue events = new SynchronizedQueue<>(); Nio2Endpoint 工作流程跟 NioEndpoint 较为相似。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127143839.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127143839.jpg) Nio2Endpoint 子组件功能说明: @@ -218,7 +219,7 @@ Tomcat 本身是 Java 编写的,为了调用 C 语言编写的 APR,需要通 ### 3.1. AprEndpoint 工作流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127145740.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127145740.jpg) #### 3.1.1. Acceptor @@ -282,7 +283,7 @@ java my.class 这个命令行中的`java`其实是**一个可执行程序,这个程序会创建 JVM 来加载和运行你的 Java 类**。操作系统会创建一个进程来执行这个`java`可执行程序,而每个进程都有自己的虚拟地址空间,JVM 用到的内存(包括堆、栈和方法区)就是从进程的虚拟地址空间上分配的。请你注意的是,JVM 内存只是进程空间的一部分,除此之外进程空间内还有代码段、数据段、内存映射区、内核空间等。从 JVM 的角度看,JVM 内存之外的部分叫作本地内存,C 程序代码在运行过程中用到的内存就是本地内存中分配的。下面我们通过一张图来理解一下。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127150729.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127150729.jpg) Tomcat 的 Endpoint 组件在接收网络数据时需要预先分配好一块 Buffer,所谓的 Buffer 就是字节数组`byte[]`,Java 通过 JNI 调用把这块 Buffer 的地址传给 C 代码,C 代码通过操作系统 API 读取 Socket 并把数据填充到这块 Buffer。Java NIO API 提供了两种 Buffer 来接收数据:HeapByteBuffer 和 DirectByteBuffer,下面的代码演示了如何创建两种 Buffer。 @@ -323,7 +324,7 @@ Tomcat 中的 AprEndpoint 就是通过 DirectByteBuffer 来接收数据的,而 从下面的图你会发现这个过程有 6 次内存拷贝,并且 read 和 write 等系统调用将导致进程从用户态到内核态的切换,会耗费大量的 CPU 和内存资源。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127151041.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127151041.jpg) 而 Tomcat 的 AprEndpoint 通过操作系统层面的 sendfile 特性解决了这个问题,sendfile 系统调用方式非常简洁。 @@ -337,7 +338,7 @@ sendfile(socket, file, len); 第二步:数据并没有从内核缓冲区复制到 Socket 关联的缓冲区,只有记录数据位置和长度的描述符被添加到 Socket 缓冲区中;接着把数据直接从内核缓冲区传递给网卡。这个过程你可以看下面的图。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127151155.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127151155.jpg) ## 4. Executor 组件 @@ -508,7 +509,7 @@ Tomcat 用 ProtocolHandler 组件屏蔽应用层协议的差异,其中 Protoco WebSocket 是通过 HTTP 协议来进行握手的,因此当 WebSocket 的握手请求到来时,HttpProtocolHandler 首先接收到这个请求,在处理这个 HTTP 请求时,Tomcat 通过一个特殊的 Filter 判断该当前 HTTP 请求是否是一个 WebSocket Upgrade 请求(即包含`Upgrade: websocket`的 HTTP 头信息),如果是,则在 HTTP 响应里添加 WebSocket 相关的响应头信息,并进行协议升级。具体来说就是用 UpgradeProtocolHandler 替换当前的 HttpProtocolHandler,相应的,把当前 Socket 的 Processor 替换成 UpgradeProcessor,同时 Tomcat 会创建 WebSocket Session 实例和 Endpoint 实例,并跟当前的 WebSocket 连接一一对应起来。这个 WebSocket 连接不会立即关闭,并且在请求处理中,不再使用原有的 HttpProcessor,而是用专门的 UpgradeProcessor,UpgradeProcessor 最终会调用相应的 Endpoint 实例来处理请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127153521.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127153521.jpg) 你可以看到,Tomcat 对 WebSocket 请求的处理没有经过 Servlet 容器,而是通过 UpgradeProcessor 组件直接把请求发到 ServerEndpoint 实例,并且 Tomcat 的 WebSocket 实现不需要关注具体 I/O 模型的细节,从而实现了与具体 I/O 方式的解耦。 diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/03.Tomcat\345\256\271\345\231\250.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/03.Tomcat\345\256\271\345\231\250.md" index c18930fb08..0f71d9ce68 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/03.Tomcat\345\256\271\345\231\250.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/03.Tomcat\345\256\271\345\231\250.md" @@ -1,6 +1,7 @@ --- title: Tomcat容器 date: 2022-02-17 22:34:30 +order: 03 categories: - Java - JavaEE @@ -335,7 +336,7 @@ Tomcat 作为 Web 容器,需要解决以下问题: 2. 两个 Web 应用都依赖同一个第三方的 JAR 包,比如 Spring,那 Spring 的 JAR 包被加载到内存后,Tomcat 要保证这两个 Web 应用能够共享,也就是说 Spring 的 JAR 包只被加载一次,否则随着依赖的第三方 JAR 包增多,JVM 的内存会膨胀。 3. 需要隔离 Tomcat 本身的类和 Web 应用的类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201130141536.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201130141536.png) #### WebAppClassLoader diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/04.Tomcat\344\274\230\345\214\226.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/04.Tomcat\344\274\230\345\214\226.md" index 74acd1a38e..3beb96e83a 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/04.Tomcat\344\274\230\345\214\226.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/04.Tomcat\344\274\230\345\214\226.md" @@ -1,6 +1,7 @@ --- title: Tomcat优化 date: 2022-02-17 22:34:30 +order: 04 categories: - Java - JavaEE diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/05.Tomcat\345\222\214Jetty.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/05.Tomcat\345\222\214Jetty.md" index b24c3cd353..a841530fb4 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/05.Tomcat\345\222\214Jetty.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/05.Tomcat\345\222\214Jetty.md" @@ -1,6 +1,7 @@ --- title: Tomcat 和 Jetty date: 2022-02-17 22:34:30 +order: 05 categories: - Java - JavaEE diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/README.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/README.md" index 86b7773721..6204b0c8d1 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/README.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/01.Tomcat/README.md" @@ -13,6 +13,7 @@ tags: - Tomcat permalink: /pages/33e817/ hidden: true +index: false --- # Tomcat 教程 @@ -32,4 +33,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/02.Jetty.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/02.Jetty.md" index 0678589412..db02b55cf8 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/02.Jetty.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/02.Jetty.md" @@ -1,6 +1,7 @@ --- title: Jetty 快速入门 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - JavaEE @@ -253,7 +254,7 @@ mvn jetty:run ### Jetty 架构简介 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127154145.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127154145.jpg) Jetty Server 就是由多个 Connector(连接器)、多个 Handler(处理器),以及一个线程池组成。 @@ -378,7 +379,7 @@ getEndPoint().fillInterested(_readCallback); 到此你应该了解了 Connector 的工作原理,下面我画张图再来回顾一下 Connector 的工作流程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201118175805.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201118175805.jpg) 1. Acceptor 监听连接请求,当有连接请求到达时就接受连接,一个连接对应一个 Channel,Acceptor 将 Channel 交给 ManagedSelector 来处理。 @@ -426,7 +427,7 @@ public interface Handler extends LifeCycle, Destroyable Handler 只是一个接口,完成具体功能的还是它的子类。那么 Handler 有哪些子类呢?它们的继承关系又是怎样的?这些子类是如何实现 Servlet 容器功能的呢? -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201118181025.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201118181025.png) 在 AbstractHandler 之下有 AbstractHandlerContainer,为什么需要这个类呢?这其实是个过渡,为了实现链式调用,一个 Handler 内部必然要有其他 Handler 的引用,所以这个类的名字里才有 Container,意思就是这样的 Handler 里包含了其他 Handler 的引用。 diff --git "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/README.md" "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/README.md" index f1079eae4b..9e9a4473ed 100644 --- "a/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/README.md" +++ "b/source/_posts/01.Java/02.JavaEE/02.\346\234\215\345\212\241\345\231\250/README.md" @@ -11,6 +11,7 @@ tags: - 服务器 permalink: /pages/e3f3f3/ hidden: true +index: false --- # Java 服务器 @@ -35,4 +36,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git a/source/_posts/01.Java/02.JavaEE/README.md b/source/_posts/01.Java/02.JavaEE/README.md index 35659fda7e..846c3677fe 100644 --- a/source/_posts/01.Java/02.JavaEE/README.md +++ b/source/_posts/01.Java/02.JavaEE/README.md @@ -9,6 +9,7 @@ tags: - JavaEE permalink: /pages/80a822/ hidden: true +index: false --- # JavaEE @@ -57,4 +58,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/01.Maven\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/01.Maven\345\277\253\351\200\237\345\205\245\351\227\250.md" index b4fa9c98ad..b2efbdda0e 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/01.Maven\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/01.Maven\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Maven 快速入门 date: 2020-02-07 23:04:47 +order: 01 categories: - Java - 软件 @@ -134,9 +135,9 @@ export PATH=$MAVEN_HOME/bin:$PATH 右键 "计算机",选择 "属性",之后点击 "高级系统设置",点击"环境变量",来设置环境变量,有以下系统变量需要配置: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200108143017.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200108143017.png) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200108143038.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200108143038.png) ### 检测安装成功 @@ -264,15 +265,15 @@ java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App 依次点击 File -> New -> Project 打开创建工程对话框,选择 Maven 工程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1555414103572.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1555414103572.png) (2)输入项目信息 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1555415549748.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1555415549748.png) (3)点击 Intellij 侧边栏中的 Maven 工具界面,有几个可以直接使用的 maven 命令,可以帮助你进行构建。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1555415806237.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1555415806237.png) ### 在 Eclipse 中创建 Maven 工程 @@ -284,7 +285,7 @@ java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App 点击 Help -> Eclipse Marketplace,搜索 maven 关键字,选择安装红框对应的 Maven 插件。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127195117.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127195117.png) (2)Maven 环境配置 @@ -292,7 +293,7 @@ java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App 如下图所示,配置 settings.xml 文件的位置 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127195128.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127195128.png) (3)创建 Maven 工程 @@ -300,7 +301,7 @@ File -> New -> Maven Project -> Next,在接下来的窗口中会看到一大 接下来设置项目的参数,如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127195151.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127195151.png) **groupId**是项目组织唯一的标识符,实际对应 JAVA 的包的结构,是 main 目录里 java 的目录结构。 @@ -314,11 +315,11 @@ Eclipse 中构建方式: 在 Elipse 项目上右击 -> Run As 就能看到很多 Maven 操作。这些操作和 maven 命令是等效的。例如 Maven clean,等同于 mvn clean 命令。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127195208.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127195208.png) 你也可以点击 Maven build,输入组合命令,并保存下来。如下图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127195219.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127195219.png) Maven 命令构建方式: @@ -326,7 +327,7 @@ Maven 命令构建方式: 进入工程所在目录,输入 maven 命令就可以了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127195243.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127195243.png) ## 使用说明 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/02.Maven\346\225\231\347\250\213\344\271\213pom.xml\350\257\246\350\247\243.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/02.Maven\346\225\231\347\250\213\344\271\213pom.xml\350\257\246\350\247\243.md" index e93862b0cc..d19300f4dd 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/02.Maven\346\225\231\347\250\213\344\271\213pom.xml\350\257\246\350\247\243.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/02.Maven\346\225\231\347\250\213\344\271\213pom.xml\350\257\246\350\247\243.md" @@ -1,6 +1,7 @@ --- title: Maven 教程之 pom.xml 详解 date: 2019-05-14 14:57:33 +order: 02 categories: - Java - 软件 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/03.Maven\346\225\231\347\250\213\344\271\213settings.xml\350\257\246\350\247\243.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/03.Maven\346\225\231\347\250\213\344\271\213settings.xml\350\257\246\350\247\243.md" index 384770c294..9fce2931ba 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/03.Maven\346\225\231\347\250\213\344\271\213settings.xml\350\257\246\350\247\243.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/03.Maven\346\225\231\347\250\213\344\271\213settings.xml\350\257\246\350\247\243.md" @@ -1,6 +1,7 @@ --- title: Maven 教程之 settings.xml 详解 date: 2019-05-14 14:57:33 +order: 03 categories: - Java - 软件 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/04.Maven\345\256\236\346\210\230\351\227\256\351\242\230\345\222\214\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/04.Maven\345\256\236\346\210\230\351\227\256\351\242\230\345\222\214\346\234\200\344\275\263\345\256\236\350\267\265.md" index 74e69a8de0..af20c6e46e 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/04.Maven\345\256\236\346\210\230\351\227\256\351\242\230\345\222\214\346\234\200\344\275\263\345\256\236\350\267\265.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/04.Maven\345\256\236\346\210\230\351\227\256\351\242\230\345\222\214\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -1,6 +1,7 @@ --- title: Maven 实战问题和最佳实践 date: 2018-11-28 09:29:22 +order: 04 categories: - Java - 软件 @@ -47,21 +48,21 @@ maven 的 JDK 源与指定的 JDK 编译版本不符。 Project SDK 是否正确 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127203324.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127203324.png) SDK 路径是否正确 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127203427.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127203427.png) - **查看 Settings > Maven 的配置** JDK for importer 是否正确 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127203408.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127203408.png) Runner 是否正确 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127203439.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127203439.png) ### 重复引入依赖 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/05.Maven\346\225\231\347\250\213\344\271\213\345\217\221\345\270\203jar\345\210\260\347\247\201\346\234\215\346\210\226\344\270\255\345\244\256\344\273\223\345\272\223.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/05.Maven\346\225\231\347\250\213\344\271\213\345\217\221\345\270\203jar\345\210\260\347\247\201\346\234\215\346\210\226\344\270\255\345\244\256\344\273\223\345\272\223.md" index f9b0ad77a7..2f29b940c2 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/05.Maven\346\225\231\347\250\213\344\271\213\345\217\221\345\270\203jar\345\210\260\347\247\201\346\234\215\346\210\226\344\270\255\345\244\256\344\273\223\345\272\223.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/05.Maven\346\225\231\347\250\213\344\271\213\345\217\221\345\270\203jar\345\210\260\347\247\201\346\234\215\346\210\226\344\270\255\345\244\256\344\273\223\345\272\223.md" @@ -1,6 +1,7 @@ --- title: Maven 教程之发布 jar 到私服或中央仓库 date: 2019-05-14 14:57:33 +order: 05 categories: - Java - 软件 @@ -31,7 +32,7 @@ permalink: /pages/7bdaf9/ 注册账号成功后,根据你 Java 包的功能分别写上`Summary`、`Description`、`Group Id`、`SCM url`以及`Project URL`等必要信息,可以参见我之前创建的 Issue:[OSSRH-36187](https://issues.sonatype.org/browse/OSSRH-36187)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181106143734.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181106143734.png) 创建完之后需要等待 Sonatype 的工作人员审核处理,审核时间还是很快的,我的审核差不多等待了两小时。当 Issue 的 Status 变为`RESOLVED`后,就可以进行下一步操作了。 @@ -307,7 +308,7 @@ gpg: unchanged: 1 进入[官方下载地址](https://www.sonatype.com/download-oss-sonatype),选择合适版本下载。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127203029.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127203029.png) 本人希望将 Nexus 部署在 Linux 机器,所以选用的是 Unix 版本。 @@ -339,13 +340,13 @@ Usage: ./nexus {start|stop|run|run-redirect|status|restart|force-reload} 启动成功后,在浏览器中访问 `http://:8081`,欢迎页面如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127203131.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127203131.png) 点击右上角 Sign in 登录,默认用户名/密码为:admin/admin123。 有必要提一下的是,在 Nexus 的 Repositories 管理页面,展示了可用的 maven 仓库,如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181127203156.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181127203156.png) > 说明: > diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/06.Maven\346\217\222\344\273\266\344\271\213\344\273\243\347\240\201\346\243\200\346\237\245.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/06.Maven\346\217\222\344\273\266\344\271\213\344\273\243\347\240\201\346\243\200\346\237\245.md" index fcc61f2b3d..561aa81a22 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/06.Maven\346\217\222\344\273\266\344\271\213\344\273\243\347\240\201\346\243\200\346\237\245.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/06.Maven\346\217\222\344\273\266\344\271\213\344\273\243\347\240\201\346\243\200\346\237\245.md" @@ -1,6 +1,7 @@ --- title: Maven 插件之代码检查 date: 2019-12-16 17:09:26 +order: 06 categories: - Java - 软件 @@ -88,7 +89,7 @@ permalink: /pages/370f1d/ scope: 可以检查的方法的范围,例如:public只能检查public修饰的方法,private可以检查所有的方法 allowMissingParamTags: 是否忽略对参数注释的检查 allowMissingThrowsTags: 是否忽略对throws注释的检查 - allowMissingReturnTag: 是否忽略对return注释的检查 --> + allowMissingReturntags: 是否忽略对return注释的检查 --> diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/README.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/README.md" index 31efbbd77e..179cb60411 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/README.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/01.Maven/README.md" @@ -12,6 +12,7 @@ tags: - Maven permalink: /pages/85f27a/ hidden: true +index: false --- # Maven 教程 @@ -45,4 +46,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/02.Ant.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/02.Ant.md" index 431766e0ee..d0b8d13979 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/02.Ant.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/02.Ant.md" @@ -1,6 +1,7 @@ --- title: Ant 简易教程 date: 2017-12-06 09:46:28 +order: 02 categories: - Java - 软件 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/README.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/README.md" index 377b7a7598..08c9c99afc 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/README.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/01.\346\236\204\345\273\272/README.md" @@ -10,6 +10,7 @@ tags: - 构建 permalink: /pages/d1859b/ hidden: true +index: false --- # Java 构建 @@ -46,4 +47,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/01.Intellij.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/01.Intellij.md" index 7836c05d92..85b4f254a7 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/01.Intellij.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/01.Intellij.md" @@ -1,6 +1,7 @@ --- title: Intellij IDEA 快速入门 date: 2019-11-29 18:10:14 +order: 01 categories: - Java - 软件 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/02.Eclipse.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/02.Eclipse.md" index 8540095abb..d8f3730d84 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/02.Eclipse.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/02.Eclipse.md" @@ -1,6 +1,7 @@ --- title: Eclipse 快速入门 date: 2018-07-01 11:27:47 +order: 02 categories: - Java - 软件 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/03.VsCode.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/03.VsCode.md" index 6f7986350d..909e80c55f 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/03.VsCode.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/03.VsCode.md" @@ -1,6 +1,7 @@ --- title: Vscode 快速入门 date: 2019-05-14 14:57:33 +order: 03 categories: - Java - 软件 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/README.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/README.md" index 9d7da4a883..e487ace7d0 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/README.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/02.IDE/README.md" @@ -10,6 +10,7 @@ tags: - IDE permalink: /pages/8695a7/ hidden: true +index: false --- # Java IDE diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/01.\347\233\221\346\216\247\345\267\245\345\205\267\345\257\271\346\257\224.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/01.\347\233\221\346\216\247\345\267\245\345\205\267\345\257\271\346\257\224.md" index 4b702af578..7ccdb46566 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/01.\347\233\221\346\216\247\345\267\245\345\205\267\345\257\271\346\257\224.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/01.\347\233\221\346\216\247\345\267\245\345\205\267\345\257\271\346\257\224.md" @@ -1,6 +1,7 @@ --- title: 监控工具对比 date: 2020-02-11 17:48:32 +order: 01 categories: - Java - 软件 @@ -15,17 +16,17 @@ permalink: /pages/16563a/ ## 监控工具发展史 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211165813.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211165813.png) ## 监控工具比对 ### 特性对比 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211171551.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211171551.png) ### 生态对比 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211172631.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211172631.png) ## 技术选型 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/02.CAT.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/02.CAT.md" index 5df47916d2..ee099d3686 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/02.CAT.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/02.CAT.md" @@ -1,6 +1,7 @@ --- title: CAT 快速入门 date: 2020-02-11 17:48:32 +order: 02 categories: - Java - 软件 @@ -36,7 +37,7 @@ CAT 监控系统将每次 URL、Service 的请求内部执行情况都封装为 - **Heartbeat** 表示程序内定期产生的统计信息, 如 CPU 利用率, 内存利用率, 连接池状态, 系统负载等 - **Metric** 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为 1 分钟 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211174235.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211174235.png) ## CAT 部署 @@ -71,7 +72,7 @@ CAT 主要分为三个模块: 在实际开发和部署中,cat-consumer 和 cat-home 是部署在一个 jvm 内部,每个 CAT 服务端都可以作为 consumer 也可以作为 home,这样既能减少整个 CAT 层级结构,也可以增加整个系统稳定性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211174001.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211174001.png) 上图是 CAT 目前多机房的整体结构图: diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/03.Zipkin.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/03.Zipkin.md" index 7d2326052b..e665495de3 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/03.Zipkin.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/03.Zipkin.md" @@ -1,6 +1,7 @@ --- title: Zipkin 快速入门 date: 2020-03-23 22:56:45 +order: 03 categories: - Java - 软件 @@ -28,7 +29,7 @@ Zipkin 基于 Google Dapper 的论文设计而来,由 Twitter 公司开发贡 Zipkin UI 还提供了一个依赖关系图,该关系图显示了每个应用程序中跟踪了多少个请求。这对于识别聚合行为(包括错误路径或对不赞成使用的服务的调用)很有帮助。 -![Zipkin UI](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211161706.png) +![Zipkin UI](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211161706.png) ### 多平台 @@ -46,7 +47,7 @@ Zipkin 服务器捆绑了用于采集和存储数据的扩展。 数据以 json 形式存储,可以参考:[Zipkin 官方的 Swagger API](https://zipkin.io/zipkin-api/#/default/post_spans) -![Zipkin Swagger API](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211162055.png) +![Zipkin Swagger API](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211162055.png) ## 二、Zipkin 安装 @@ -92,7 +93,7 @@ ZipKin 可以分为两部分, 架构如下: -![Zipkin 架构](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211155836.png) +![Zipkin 架构](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211155836.png) ### Zipkin Server diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/04.Skywalking.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/04.Skywalking.md" index 4ce536ccd9..e357343780 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/04.Skywalking.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/04.Skywalking.md" @@ -1,6 +1,7 @@ --- title: SkyWalking 快速入门 date: 2020-02-07 23:04:47 +order: 04 categories: - Java - 软件 @@ -22,7 +23,7 @@ SkyWalking 是观察性分析平台和应用性能管理系统。 提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211152235.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211152235.png) ### SkyWalking 特性 @@ -43,7 +44,7 @@ SkyWalking 是观察性分析平台和应用性能管理系统。 从逻辑上讲,SkyWalking 分为四个部分:探针(Probes),平台后端,存储和 UI。 -![SkyWalking 架构](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211153516.png) +![SkyWalking 架构](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211153516.png) - **探针(Probes)** - 探针是指集成到目标系统中的代理或 SDK 库。它们负责收集数据(包括跟踪数据和统计数据)并将其按照 SkyWalking 的要求重新格式化为。 - **平台后端** - 平台后端是一个提供后端服务的集群。它用于聚合、分析和驱动从探针到 UI 的流程。它还为传入格式(如 Zipkin 的格式),存储实现程序和集群管理提供可插入功能。 您甚至可以使用 Observability Analysis Language 自定义聚合和分析。 @@ -54,7 +55,7 @@ SkyWalking 是观察性分析平台和应用性能管理系统。 进入 [Apache SkyWalking 官方下载页面](http://skywalking.apache.org/downloads/),选择安装版本,下载解压到本地。 -![SkyWalking 组件](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211154612.png) +![SkyWalking 组件](https://raw.githubusercontent.com/dunwu/images/master/snap/20200211154612.png) 安装分为三个部分: diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/05.Arthas.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/05.Arthas.md" index 9a43801f61..7e5726c964 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/05.Arthas.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/05.Arthas.md" @@ -1,6 +1,7 @@ --- title: Arthas 快速入门 date: 2020-02-07 23:04:47 +order: 05 categories: - Java - 软件 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/README.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/README.md" index 2518f80ead..a2caaea0a2 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/README.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/03.\347\233\221\346\216\247\350\257\212\346\226\255/README.md" @@ -11,6 +11,7 @@ tags: - 诊断 permalink: /pages/3d16d3/ hidden: true +index: false --- # Java 监控诊断 diff --git "a/source/_posts/01.Java/11.\350\275\257\344\273\266/README.md" "b/source/_posts/01.Java/11.\350\275\257\344\273\266/README.md" index f59129030c..d44fbe38b5 100644 --- "a/source/_posts/01.Java/11.\350\275\257\344\273\266/README.md" +++ "b/source/_posts/01.Java/11.\350\275\257\344\273\266/README.md" @@ -8,6 +8,7 @@ tags: - Java permalink: /pages/2cb045/ hidden: true +index: false --- # Java 软件 @@ -66,4 +67,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/01.JSON\345\272\217\345\210\227\345\214\226.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/01.JSON\345\272\217\345\210\227\345\214\226.md" index 890f4dd803..2e3cfea855 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/01.JSON\345\272\217\345\210\227\345\214\226.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/01.JSON\345\272\217\345\210\227\345\214\226.md" @@ -1,6 +1,7 @@ --- title: Java 和 JSON 序列化 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/02.\344\272\214\350\277\233\345\210\266\345\272\217\345\210\227\345\214\226.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/02.\344\272\214\350\277\233\345\210\266\345\272\217\345\210\227\345\214\226.md" index 519eae8cbc..631a5fcbb5 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/02.\344\272\214\350\277\233\345\210\266\345\272\217\345\210\227\345\214\226.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/02.\344\272\214\350\277\233\345\210\266\345\272\217\345\210\227\345\214\226.md" @@ -1,6 +1,7 @@ --- title: Java 二进制序列化 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 工具 @@ -21,7 +22,7 @@ permalink: /pages/08d872/ 原因很简单,就是 Java 默认的序列化机制(`ObjectInputStream` 和 `ObjectOutputStream`)具有很多缺点。 -> 不了解 Java 默认的序列化机制,可以参考:[Java 序列化](https://dunwu.github.io/blog/pages/2b2f0f/) +> 不了解 Java 默认的序列化机制,可以参考:[Java 序列化](https://dunwu.github.io/waterdrop/pages/2b2f0f/) Java 自身的序列化方式具有以下缺点: diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/README.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/README.md" index ffe6fb56f0..1cd5ba9503 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/README.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/01.IO/README.md" @@ -11,11 +11,12 @@ tags: - 序列化 permalink: /pages/08b504/ hidden: true +index: false --- # Java 序列化工具 -Java 官方的序列化存在许多问题,因此,很多人更愿意使用优秀的第三方序列化工具来替代 Java 自身的序列化机制。 如果想详细了解 Java 自身序列化方式,可以参考:[Java 序列化](https://dunwu.github.io/blog/pages/2b2f0f/) +Java 官方的序列化存在许多问题,因此,很多人更愿意使用优秀的第三方序列化工具来替代 Java 自身的序列化机制。 如果想详细了解 Java 自身序列化方式,可以参考:[Java 序列化](https://dunwu.github.io/waterdrop/pages/2b2f0f/) 序列化库技术选型: diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/01.Lombok.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/01.Lombok.md" index 2708ec0ca6..39b1df3a5d 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/01.Lombok.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/01.Lombok.md" @@ -1,6 +1,7 @@ --- title: Lombok 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/02.Dozer.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/02.Dozer.md" index fdf13bc19b..e9a7c23031 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/02.Dozer.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/02.JavaBean/02.Dozer.md" @@ -1,6 +1,7 @@ --- title: Dozer 快速入门 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/01.Freemark.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/01.Freemark.md" index c290ba1bd1..8de94dbca1 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/01.Freemark.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/01.Freemark.md" @@ -1,6 +1,7 @@ --- title: Freemark 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 工具 @@ -33,7 +34,7 @@ Freemark 模板一句话概括就是:**_`模板 + 数据模型 = 输出`_** - **FTL 标签**:FTL 标签和 HTML 标签很相似,但是它们却是给 FreeMarker 的指示, 而且不会打印在输出内容中。 - **注释**:注释和 HTML 的注释也很相似,但它们是由 `<#--` 和 `-->`来分隔的。注释会被 FreeMarker 直接忽略, 更不会在输出内容中显示。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/ftl-template.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/ftl-template.png) > 🔔 注意: > diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/02.Thymeleaf.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/02.Thymeleaf.md" index ba54ad956a..df2e8fc48f 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/02.Thymeleaf.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/02.Thymeleaf.md" @@ -1,6 +1,7 @@ --- title: Thymeleaf 快速入门 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/03.Velocity.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/03.Velocity.md" index 6ed2c4a651..ccb6514f34 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/03.Velocity.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/03.Velocity.md" @@ -1,6 +1,7 @@ --- title: Velocity 快速入门 date: 2022-02-17 22:34:30 +order: 03 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/README.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/README.md" index 9ae65e4b7b..ffd80ba350 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/README.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/03.\346\250\241\346\235\277\345\274\225\346\223\216/README.md" @@ -10,6 +10,7 @@ tags: - 模板引擎 permalink: /pages/9d37fa/ hidden: true +index: false --- # Java 模板引擎 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/01.Junit.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/01.Junit.md" index 1506a00b07..b1d02cb99f 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/01.Junit.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/01.Junit.md" @@ -1,6 +1,7 @@ --- title: JUnit5 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 工具 @@ -61,7 +62,7 @@ JUnit 5 在运行时需要 Java 8(或更高版本)。 组件间依赖关系: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/test/junit/junit5-components.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/test/junit/junit5-components.png) ## JUnit5 注解 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/02.Mockito.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/02.Mockito.md" index 0b0898d298..e33ffaa498 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/02.Mockito.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/02.Mockito.md" @@ -1,6 +1,7 @@ --- title: Mockito 快速入门 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/03.Jmeter.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/03.Jmeter.md" index 18e9eb0d0f..7378e09f5d 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/03.Jmeter.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/03.Jmeter.md" @@ -1,6 +1,7 @@ --- title: JMeter 快速入门 date: 2022-02-17 22:34:30 +order: 03 categories: - Java - 工具 @@ -43,7 +44,7 @@ Jmeter 的工作原理是仿真用户向服务器发送请求,并收集服务 Jmeter 的工作流如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/test/jmeter-workflow.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/test/jmeter-workflow.png) ### 主要元素 @@ -63,7 +64,7 @@ Jmeter 的主要元素如下: - **`预处理器元素(Pre-Processor Elements)`** - 预处理器元素在采样器发出请求之前执行,如果预处理器附加到采样器元素,那么它将在该采样器元素运行之前执行。预处理器元素用于在运行之前准备环境及参数。 - **`后处理器元素(Post-Processor Elements)`** - 后处理器元素是在发送采样器请求之后执行的元素,常用于处理响应数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/test/jmeter-elements.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/test/jmeter-elements.png) > 📌 提示: > @@ -97,7 +98,7 @@ Jmeter 的主要元素如下: Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` -![image-20191024104517721](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024104517721.png) +![image-20191024104517721](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024104517721.png) ## 使用 @@ -115,7 +116,7 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` - 设置线程数和循环次数 -![image-20191024105545736](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024105545736.png) +![image-20191024105545736](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024105545736.png) #### 配置原件 @@ -123,7 +124,7 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` - 填写协议、服务器名称或 IP、端口号 -![image-20191024110016264](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024110016264.png) +![image-20191024110016264](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024110016264.png) #### 构造 HTTP 请求 @@ -133,35 +134,35 @@ Unix 类系统运行 `jmeter` ;Windows 系统运行 `jmeter.bat` - 填写方法、路径 - 填写参数、消息体数据、文件上传 -![image-20191024110953063](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024110953063.png) +![image-20191024110953063](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024110953063.png) #### 添加 HTTP 请求头 - 在“线程组”上右键 【添加】=>【配置元件】=>【HTTP 信息头管理器】 - 由于我的测试例中传输的数据为 json 形式,所以设置键值对 `Content-Type`:`application/json` -![image-20191024111825226](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024111825226.png) +![image-20191024111825226](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024111825226.png) #### 添加断言 - 在“线程组”上右键 【添加】=>【断言】=>【 响应断言 】 - 在我的案例中,以 HTTP 应答状态码为 200 来判断请求是否成功 -![image-20191024112335130](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024112335130.png) +![image-20191024112335130](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024112335130.png) #### 添加察看结果树 - 在“线程组”上右键 【添加】=>【监听器】=>【察看结果树】 - 直接点击运行,就可以查看测试结果 -![image-20191024113849270](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024113849270.png) +![image-20191024113849270](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024113849270.png) #### 添加汇总报告 - 在“线程组”上右键 【添加】=>【监听器】=>【汇总报告】 - 直接点击运行,就可以查看测试结果 -![image-20191024114016424](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024114016424.png) +![image-20191024114016424](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024114016424.png) #### 保存测试计划 @@ -179,7 +180,7 @@ jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] 执行测试计划后,在 `-e -o` 参数后指定的 web 报告目录下,可以找到测试报告内容。在浏览器中打开 `index.html` 文件,可以看到如下报告: -![image-20191024120233058](https://raw.githubusercontent.com/dunwu/images/dev/snap/jmeter/image-20191024120233058.png) +![image-20191024120233058](https://raw.githubusercontent.com/dunwu/images/master/snap/jmeter/image-20191024120233058.png) ## 问题 @@ -191,7 +192,7 @@ jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder] 配置如下所示: -![image-20191127175820747](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20191127175820747.png) +![image-20191127175820747](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20191127175820747.png) 重要配置说明(其他配置根据实际情况填): diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/04.JMH.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/04.JMH.md" index 6be976cc0d..e5707faf22 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/04.JMH.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/04.JMH.md" @@ -1,6 +1,7 @@ --- title: JMH 快速入门 date: 2022-02-17 22:34:30 +order: 04 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/README.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/README.md" index 29bec69998..ab247b863e 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/README.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/04.\346\265\213\350\257\225/README.md" @@ -10,6 +10,7 @@ tags: - 测试 permalink: /pages/2cecc3/ hidden: true +index: false --- # Java 测试 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.Java\346\227\245\345\277\227.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.Java\346\227\245\345\277\227.md" index 78fe55dbeb..efdc23d614 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.Java\346\227\245\345\277\227.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.Java\346\227\245\345\277\227.md" @@ -1,6 +1,7 @@ --- title: javalib-log date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 工具 @@ -63,7 +64,7 @@ logback 当前分成三个模块:`logback-core`、`logback-classic` 和 `logba Log4j2 架构: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/log/log4j2-architecture.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/log/log4j2-architecture.jpg) ### Log4j vs Logback vs Log4j2 @@ -107,7 +108,7 @@ common-logging 的功能是提供日志功能的 API 接口,本身并不提供 [官网地址](http://www.slf4j.org/) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/log/slf4j-to-other-log.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/log/slf4j-to-other-log.png) ### common-logging vs slf4j @@ -201,7 +202,7 @@ _slf4j-jdk14-1.7.21.jar_ 会自动将 _slf4j-api-1.7.21.jar_ 也添加到你的 假如你正在开发应用程序所调用的组件当中已经使用了 common-logging,这时你需要 jcl-over-slf4j.jar 把日志信息输出重定向到 slf4j-api,slf4j-api 再去调用 slf4j 实际依赖的日志组件。这个过程称为桥接。下图是官方的 slf4j 桥接策略图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/log/slf4j-bind-strategy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/log/slf4j-bind-strategy.png) 从图中应该可以看出,无论你的老项目中使用的是 common-logging 或是直接使用 log4j、java.util.logging,都可以使用对应的桥接 jar 包来解决兼容问题。 @@ -417,7 +418,7 @@ log4j2 基本配置形式如下: - 要点 - 它有 ``、``、`` 三个子元素。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/log/logback-configuration.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/log/logback-configuration.png) ### `` @@ -455,7 +456,7 @@ log4j2 基本配置形式如下: - 属性 - class:设置具体的实例化类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/log/logback-appender.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/log/logback-appender.png) ### `` diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/02.Java\345\267\245\345\205\267\345\214\205.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/02.Java\345\267\245\345\205\267\345\214\205.md" index b14ec41861..192f3c4216 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/02.Java\345\267\245\345\205\267\345\214\205.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/02.Java\345\267\245\345\205\267\345\214\205.md" @@ -1,6 +1,7 @@ --- title: javalib-util date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/03.Reflections.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/03.Reflections.md" index 052945b156..6200ebea4f 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/03.Reflections.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/03.Reflections.md" @@ -1,6 +1,7 @@ --- title: Reflections 快速入门 date: 2022-02-17 22:34:30 +order: 03 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/04.JavaMail.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/04.JavaMail.md" index 0cb6fb225e..dc623bbfc1 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/04.JavaMail.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/04.JavaMail.md" @@ -1,6 +1,7 @@ --- title: JavaMail 快速入门 date: 2022-02-17 22:34:30 +order: 04 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/05.Jsoup.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/05.Jsoup.md" index f3fb258bba..04f0cbd5a0 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/05.Jsoup.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/05.Jsoup.md" @@ -1,6 +1,7 @@ --- title: Jsoup 快速入门 date: 2022-02-17 22:34:30 +order: 05 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/06.Thumbnailator.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/06.Thumbnailator.md" index 6ef402d984..7115115416 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/06.Thumbnailator.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/06.Thumbnailator.md" @@ -1,6 +1,7 @@ --- title: Thumbnailator 快速入门 date: 2022-02-17 22:34:30 +order: 06 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/07.Zxing.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/07.Zxing.md" index e93951096e..d5f691bb90 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/07.Zxing.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/99.\345\205\266\344\273\226/07.Zxing.md" @@ -1,6 +1,7 @@ --- title: ZXing 快速入门 date: 2022-02-17 22:34:30 +order: 07 categories: - Java - 工具 diff --git "a/source/_posts/01.Java/12.\345\267\245\345\205\267/README.md" "b/source/_posts/01.Java/12.\345\267\245\345\205\267/README.md" index 6809c9489d..dbd1c924e8 100644 --- "a/source/_posts/01.Java/12.\345\267\245\345\205\267/README.md" +++ "b/source/_posts/01.Java/12.\345\267\245\345\205\267/README.md" @@ -9,6 +9,7 @@ tags: - 工具 permalink: /pages/1123e1/ hidden: true +index: false --- # Java 工具 @@ -52,4 +53,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/01.Spring\346\246\202\350\277\260.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/01.Spring\346\246\202\350\277\260.md" index 8a16810581..3a202241c6 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/01.Spring\346\246\202\350\277\260.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/01.Spring\346\246\202\350\277\260.md" @@ -1,6 +1,7 @@ --- title: Spring Framework 综述 date: 2019-11-22 10:46:02 +order: 01 categories: - Java - 框架 @@ -90,7 +91,7 @@ Spring 当前框架有**20**个 jar 包,大致可以分为**6**大模块: Spring 框架提供了非常丰富的功能,因此整个架构也很庞大。 在我们实际的应用开发中,并不一定要使用所有的功能,而是可以根据需要选择合适的 Spring 模块。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/spring-framework.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/spring-framework.png) ### Core Container diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/21.SpringBoot\347\237\245\350\257\206\345\233\276\350\260\261.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/21.SpringBoot\347\237\245\350\257\206\345\233\276\350\260\261.md" index 0099a8b2d4..3a5733b4c0 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/21.SpringBoot\347\237\245\350\257\206\345\233\276\350\260\261.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/21.SpringBoot\347\237\245\350\257\206\345\233\276\350\260\261.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 知识图谱 date: 2020-08-12 07:01:26 +order: 21 categories: - Java - 框架 @@ -108,8 +109,6 @@ public interface BeanPostProcessor { 为了理解这两个方法执行的时机,简单的了解下 bean 的整个生命周期: -![img](data:image/svg+xml;utf8,) _Bean 的实例化过程(来自:Spring 揭秘)_ - `postProcessBeforeInitialization()`方法与`postProcessAfterInitialization()`分别对应图中前置处理和后置处理两个步骤将执行的方法。这两个方法中都传入了 bean 对象实例的引用,为扩展容器的对象实例化过程提供了很大便利,在这儿几乎可以对传入的实例执行任何操作。注解、AOP 等功能的实现均大量使用了`BeanPostProcessor`,比如有一个自定义注解,你完全可以实现 BeanPostProcessor 的接口,在其中判断 bean 对象的脑袋上是否有该注解,如果有,你可以对这个 bean 实例执行任何操作,想想是不是非常的简单? 再来看一个更常见的例子,在 Spring 中经常能够看到各种各样的 Aware 接口,其作用就是在对象实例化完成以后将 Aware 接口定义中规定的依赖注入到当前实例中。比如最常见的`ApplicationContextAware`接口,实现了这个接口的类都可以获取到一个 ApplicationContext 对象。当容器中每个对象的实例化过程走到 BeanPostProcessor 前置处理这一步时,容器会检测到之前注册到容器的 ApplicationContextAwareProcessor,然后就会调用其 postProcessBeforeInitialization()方法,检查并设置 Aware 相关依赖。看看代码吧,是不是很简单: @@ -932,7 +931,7 @@ public void initialize(ConfigurableApplicationContext context) { } ``` -然后你就可以进入到`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`方法了,这个方法除了会遍历上面的 3 个 BeanFactoryPostProcessor 处理外,还会获取类型为`BeanDefinitionRegistryPostProcessor`的 bean:`org.springframework.context.annotation.internalConfigurationAnnotationProcessor`,对应的 Class 为`ConfigurationClassPostProcessor`。`ConfigurationClassPostProcessor`用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理`@import`注解的时候,就会调用<自动配置>这一小节中的`EnableAutoConfigurationImportSelector.selectImports()`来完成自动配置功能。其他的这里不再多讲,如果你有兴趣,可以查阅参考资料 6。 +然后你就可以进入到`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`方法了,这个方法除了会遍历上面的 3 个 BeanFactoryPostProcessor 处理外,还会获取类型为`BeanDefinitionRegistryPostProcessor`的 bean:`org.springframework.context.annotation.internalConfigurationAnnotationProcessor`,对应的 Class 为`ConfigurationClassPostProcessor`。`ConfigurationClassPostProcessor`用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理`@import`注解的时候,就会调用自动配置这一小节中的`EnableAutoConfigurationImportSelector.selectImports()`来完成自动配置功能。其他的这里不再多讲,如果你有兴趣,可以查阅参考资料 6。 ⑧、查找当前 context 中是否注册有 CommandLineRunner 和 ApplicationRunner,如果有则遍历执行它们。 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/22.SpringBoot\345\237\272\346\234\254\345\216\237\347\220\206.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/22.SpringBoot\345\237\272\346\234\254\345\216\237\347\220\206.md" index 874a71e7c0..a7ee34aeb5 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/22.SpringBoot\345\237\272\346\234\254\345\216\237\347\220\206.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/22.SpringBoot\345\237\272\346\234\254\345\216\237\347\220\206.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 基本原理 date: 2020-08-12 07:01:26 +order: 22 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/99.Spring\351\235\242\350\257\225.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/99.Spring\351\235\242\350\257\225.md" index 5bdd0c958c..461f16fb66 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/99.Spring\351\235\242\350\257\225.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/99.Spring\351\235\242\350\257\225.md" @@ -1,6 +1,7 @@ --- title: Spring 面试 date: 2018-08-02 17:33:32 +order: 99 categories: - Java - 框架 @@ -52,7 +53,7 @@ permalink: /pages/db33b0/ ### Spring Framework 中有多少个模块,它们分别是什么? -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/spring-framework.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/spring-framework.png) - **Spring 核心容器** – 该层基本上是 Spring Framework 的核心。它包含以下模块: - Spring Core @@ -115,7 +116,7 @@ IoC 的实现方式有两种: Spring IoC 是 IoC 的一种实现。DI 是 Spring IoC 的主要实现原则。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20221005163639.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20221005163639.png) #### 依赖注入有哪些实现方式? @@ -283,7 +284,7 @@ Spring bean 支持 5 种 scope: #### Spring Bean 的生命周期 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20211201102734.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20211201102734.png) spring bean 容器的生命周期如下: @@ -370,7 +371,7 @@ AOP(Aspect-Oriented Programming), 即 **面向切面编程**, 它与 OOP( Object #### AOP 中的 Aspect、Advice、Pointcut、JointPoint 和 Advice 参数分别是什么? -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/core/spring-aop.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/core/spring-aop.png) - **Aspect** - Aspect 是一个实现交叉问题的类,例如事务管理。方面可以是配置的普通类,然后在 Spring Bean 配置文件中配置,或者我们可以使用 Spring AspectJ 支持使用 @Aspect 注解将类声明为 Aspect。 - **Advice** - Advice 是针对特定 JoinPoint 采取的操作。在编程方面,它们是在应用程序中达到具有匹配切入点的特定 JoinPoint 时执行的方法。您可以将 Advice 视为 Spring 拦截器(Interceptor)或 Servlet 过滤器(filter)。 @@ -538,7 +539,7 @@ Spring DAO 使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易 ### 列举 Spring DAO 抛出的异常。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/data-access/spring-data-access-exception.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/data-access/spring-data-access-exception.png) ### spring JDBC API 中存在哪些类? @@ -580,10 +581,10 @@ Spring Web MVC 框架提供 **模型-视图-控制器** 架构和随时可用的 DispatcherServlet 的工作流程可以用一幅图来说明: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/web/spring-dispatcher-servlet.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/web/spring-dispatcher-servlet.png) 1. 向服务器发送 HTTP 请求,请求被前端控制器 `DispatcherServlet` 捕获。 -2. `DispatcherServlet` 根据 **-servlet.xml** 中的配置对请求的 URL 进行解析,得到请求资源标识符(URI)。然后根据该 URI,调用 `HandlerMapping` 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以`HandlerExecutionChain` 对象的形式返回。 +2. `DispatcherServlet` 根据 **`-servlet.xml`** 中的配置对请求的 URL 进行解析,得到请求资源标识符(URI)。然后根据该 URI,调用 `HandlerMapping` 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以`HandlerExecutionChain` 对象的形式返回。 3. `DispatcherServlet` 根据获得的`Handler`,选择一个合适的 `HandlerAdapter`。(附注:如果成功获得`HandlerAdapter`后,此时将开始执行拦截器的 preHandler(...)方法)。 4. 提取`Request`中的模型数据,填充`Handler`入参,开始执行`Handler`(`Controller`)。 在填充`Handler`的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作: - HttpMessageConveter: 将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的响应信息。 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/README.md" index 39ba26845d..cb55bf5800 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/00.Spring\347\273\274\345\220\210/README.md" @@ -13,6 +13,7 @@ tags: - SpringBoot permalink: /pages/9e0b67/ hidden: true +index: false --- # Spring 综述 @@ -39,4 +40,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/01.SpringBean.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/01.SpringBean.md" index c548348b6a..1df26d7b49 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/01.SpringBean.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/01.SpringBean.md" @@ -1,6 +1,7 @@ --- title: Spring Bean date: 2021-12-10 19:15:42 +order: 01 categories: - Java - 框架 @@ -101,7 +102,7 @@ Bean 别名(Alias)的作用: ## Spring Bean 生命周期 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20211201102734.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20211201102734.png) 1. Spring 对 Bean 进行实例化(相当于 new XXX()) diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/02.SpringIoC.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/02.SpringIoC.md" index 3619e4d705..505172fbe8 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/02.SpringIoC.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/02.SpringIoC.md" @@ -1,6 +1,7 @@ --- title: Spring IoC date: 2020-08-30 16:06:10 +order: 02 categories: - Java - 框架 @@ -32,7 +33,7 @@ IoC 的实现方式有两种: - **谁控制谁,控制什么**:传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 Ioc 容器来控制对象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。 - **为何是反转,哪些方面反转了**:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20221006120112.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20221006120112.png) ### IoC 能做什么 @@ -95,7 +96,7 @@ BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig. 下图显示了 Spring IoC 容器的工作步骤 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200723102456.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200723102456.png) 使用 IoC 容器可分为三步骤: diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/03.Spring\344\276\235\350\265\226\346\237\245\346\211\276.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/03.Spring\344\276\235\350\265\226\346\237\245\346\211\276.md" index 49de8b6a63..0d1fa3385f 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/03.Spring\344\276\235\350\265\226\346\237\245\346\211\276.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/03.Spring\344\276\235\350\265\226\346\237\245\346\211\276.md" @@ -1,6 +1,7 @@ --- title: Spring 依赖查找 date: 2020-08-30 16:06:10 +order: 03 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/04.Spring\344\276\235\350\265\226\346\263\250\345\205\245.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/04.Spring\344\276\235\350\265\226\346\263\250\345\205\245.md" index 27a391f2c3..c44e438f49 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/04.Spring\344\276\235\350\265\226\346\263\250\345\205\245.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/04.Spring\344\276\235\350\265\226\346\263\250\345\205\245.md" @@ -1,6 +1,7 @@ --- title: Spring 依赖注入 date: 2020-08-30 16:06:10 +order: 04 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/05.SpringIoC\344\276\235\350\265\226\346\235\245\346\272\220.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/05.SpringIoC\344\276\235\350\265\226\346\235\245\346\272\220.md" index f51fb95171..b633b85336 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/05.SpringIoC\344\276\235\350\265\226\346\235\245\346\272\220.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/05.SpringIoC\344\276\235\350\265\226\346\235\245\346\272\220.md" @@ -1,6 +1,7 @@ --- title: Spring IoC 依赖来源 date: 2022-12-20 20:33:51 +order: 05 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/06.SpringBean\344\275\234\347\224\250\345\237\237.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/06.SpringBean\344\275\234\347\224\250\345\237\237.md" index 154953de7d..c07dacace0 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/06.SpringBean\344\275\234\347\224\250\345\237\237.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/06.SpringBean\344\275\234\347\224\250\345\237\237.md" @@ -1,6 +1,7 @@ --- title: Spring Bean 作用域 date: 2022-12-21 11:42:00 +order: 06 categories: - Java - 框架 @@ -28,7 +29,7 @@ permalink: /pages/8289f5/ ## "singleton" Bean 作用域 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20221221170833.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20221221170833.png) ## "prototype" Bean 作用域 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/07.SpringBean\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/07.SpringBean\347\224\237\345\221\275\345\221\250\346\234\237.md" index 81ffea93f7..4a72cd8c23 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/07.SpringBean\347\224\237\345\221\275\345\221\250\346\234\237.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/07.SpringBean\347\224\237\345\221\275\345\221\250\346\234\237.md" @@ -1,6 +1,7 @@ --- title: Spring Bean 生命周期 date: 2022-12-21 19:26:01 +order: 07 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/08.Spring\351\205\215\347\275\256\345\205\203\346\225\260\346\215\256.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/08.Spring\351\205\215\347\275\256\345\205\203\346\225\260\346\215\256.md" index a66071b775..5415034345 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/08.Spring\351\205\215\347\275\256\345\205\203\346\225\260\346\215\256.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/08.Spring\351\205\215\347\275\256\345\205\203\346\225\260\346\215\256.md" @@ -1,6 +1,7 @@ --- title: Spring 配置元数据 date: 2022-12-21 19:49:48 +order: 08 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/09.Spring\345\272\224\347\224\250\344\270\212\344\270\213\346\226\207\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/09.Spring\345\272\224\347\224\250\344\270\212\344\270\213\346\226\207\347\224\237\345\221\275\345\221\250\346\234\237.md" index a87997bb35..fea4b39844 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/09.Spring\345\272\224\347\224\250\344\270\212\344\270\213\346\226\207\347\224\237\345\221\275\345\221\250\346\234\237.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/09.Spring\345\272\224\347\224\250\344\270\212\344\270\213\346\226\207\347\224\237\345\221\275\345\221\250\346\234\237.md" @@ -1,6 +1,7 @@ --- title: Spring 应用上下文生命周期 date: 2022-12-23 09:58:09 +order: 09 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/10.SpringAop.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/10.SpringAop.md" index efa7690664..1a89468ae4 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/10.SpringAop.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/10.SpringAop.md" @@ -1,6 +1,7 @@ --- title: Spring AOP date: 2020-02-26 23:47:47 +order: 10 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/20.Spring\350\265\204\346\272\220\347\256\241\347\220\206.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/20.Spring\350\265\204\346\272\220\347\256\241\347\220\206.md" index f3f0bc55df..b285f66344 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/20.Spring\350\265\204\346\272\220\347\256\241\347\220\206.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/20.Spring\350\265\204\346\272\220\347\256\241\347\220\206.md" @@ -1,6 +1,7 @@ --- title: Spring 资源管理 date: 2019-09-04 19:46:41 +order: 20 categories: - Java - 框架 @@ -70,7 +71,7 @@ public interface Resource extends InputStreamSource { | 编码资源 | `org.springframework.core.io.support.EncodedResource` | | 上下文资源 | `org.springframework.core.io.ContextResource` | -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20221223155859.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20221223155859.png) ## 内置的 Resource 实现 @@ -101,7 +102,7 @@ public interface ResourceLoader { Spring 中主要的 ResourceLoader 实现: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20221223164745.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20221223164745.png) Spring 中,所有的 `ApplicationContext` 都实现了 `ResourceLoader` 接口。因此,所有 `ApplicationContext` 都可以通过 `getResource()` 方法获取 `Resource` 实例。 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/21.Spring\346\240\241\351\252\214.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/21.Spring\346\240\241\351\252\214.md" index 24b3ea8c48..d99a6fcc6d 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/21.Spring\346\240\241\351\252\214.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/21.Spring\346\240\241\351\252\214.md" @@ -1,6 +1,7 @@ --- title: Spring 校验 date: 2022-12-22 17:42:28 +order: 21 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/22.Spring\346\225\260\346\215\256\347\273\221\345\256\232.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/22.Spring\346\225\260\346\215\256\347\273\221\345\256\232.md" index c1ed8146ea..3c5fe26013 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/22.Spring\346\225\260\346\215\256\347\273\221\345\256\232.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/22.Spring\346\225\260\346\215\256\347\273\221\345\256\232.md" @@ -1,6 +1,7 @@ --- title: Spring 数据绑定 date: 2022-12-22 19:26:57 +order: 22 categories: - Java - 框架 @@ -20,7 +21,7 @@ permalink: /pages/267b4c/ 在 Spring 中,数据绑定功能主要由 `DataBinder` 类实现。此外,`BeanWrapper` 也具有类似的功能,但 `DataBinder` 额外支持字段验证、字段格式化和绑定结果分析。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230111150930.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230111150930.png) ## 快速入门 @@ -77,7 +78,7 @@ public class DataBindingDemo { 在 Spring 中,`DataBinder` 类是数据绑定功能的基类。`WebDataBinder` 是 `DataBinder` 的子类,主要用于 Spring Web 数据绑定,此外,还有一些 `WebDataBinder` 的扩展子类,其类族如下图所示: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230111152225.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230111152225.png) DataBinder 核心属性: diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/23.Spring\347\261\273\345\236\213\350\275\254\346\215\242.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/23.Spring\347\261\273\345\236\213\350\275\254\346\215\242.md" index 9bb441c7bb..2da331a65f 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/23.Spring\347\261\273\345\236\213\350\275\254\346\215\242.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/23.Spring\347\261\273\345\236\213\350\275\254\346\215\242.md" @@ -1,6 +1,7 @@ --- title: Spring 类型转换 date: 2022-12-22 19:43:59 +order: 23 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/24.SpringEL.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/24.SpringEL.md" index 1c5bda99e8..1e93903878 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/24.SpringEL.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/24.SpringEL.md" @@ -1,6 +1,7 @@ --- title: Spring EL 表达式 date: 2023-01-12 20:26:46 +order: 24 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/25.Spring\344\272\213\344\273\266.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/25.Spring\344\272\213\344\273\266.md" index 01c15cc43e..8dc880d04a 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/25.Spring\344\272\213\344\273\266.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/25.Spring\344\272\213\344\273\266.md" @@ -1,6 +1,7 @@ --- title: Spring 事件 date: 2022-12-22 20:31:02 +order: 25 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/26.Spring\345\233\275\351\231\205\345\214\226.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/26.Spring\345\233\275\351\231\205\345\214\226.md" index 9498844c00..e07843fd72 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/26.Spring\345\233\275\351\231\205\345\214\226.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/26.Spring\345\233\275\351\231\205\345\214\226.md" @@ -1,6 +1,7 @@ --- title: Spring 国际化 date: 2022-12-22 11:44:54 +order: 26 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/27.Spring\346\263\233\345\236\213\345\244\204\347\220\206.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/27.Spring\346\263\233\345\236\213\345\244\204\347\220\206.md" index e94c0272b8..ffc2db656b 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/27.Spring\346\263\233\345\236\213\345\244\204\347\220\206.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/27.Spring\346\263\233\345\236\213\345\244\204\347\220\206.md" @@ -1,6 +1,7 @@ --- title: Spring 泛型处理 date: 2022-12-22 20:11:52 +order: 27 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/28.Spring\346\263\250\350\247\243.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/28.Spring\346\263\250\350\247\243.md" index 6c0b8fcee0..e8ce595cfb 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/28.Spring\346\263\250\350\247\243.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/28.Spring\346\263\250\350\247\243.md" @@ -1,6 +1,7 @@ --- title: Spring 注解 date: 2022-12-23 09:08:15 +order: 28 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/29.SpringEnvironment\346\212\275\350\261\241.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/29.SpringEnvironment\346\212\275\350\261\241.md" index 95ae560160..e95e298af1 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/29.SpringEnvironment\346\212\275\350\261\241.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/29.SpringEnvironment\346\212\275\350\261\241.md" @@ -1,6 +1,7 @@ --- title: Spring Environment 抽象 date: 2022-12-23 09:27:44 +order: 29 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/31.SpringBoot\344\271\213\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/31.SpringBoot\344\271\213\345\277\253\351\200\237\345\205\245\351\227\250.md" index f1d8f8858d..da73823df5 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/31.SpringBoot\344\271\213\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/31.SpringBoot\344\271\213\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之快速入门 date: 2021-12-10 18:22:26 +order: 31 categories: - Java - 框架 @@ -254,14 +255,14 @@ $ java -jar target/myproject-0.0.1-SNAPSHOT.jar 1. 访问:`http://start.spring.io/` 2. 选择构建工具`Maven Project`、Spring Boot 版本 `1.5.10` 以及一些工程基本信息,可参考下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/start.spring.io.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/start.spring.io.png) 3. 点击`Generate Project`下载项目压缩包 4. 解压压缩包,包中已是一个完整的项目。 如果你使用 Intellij 作为 IDE,那么你可以直接使用 SPRING INITIALIZR,参考下图操作: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/intellij-spring-initializr.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/intellij-spring-initializr.gif) ### 项目说明 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/32.SpringBoot\344\271\213\345\261\236\346\200\247\345\212\240\350\275\275.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/32.SpringBoot\344\271\213\345\261\236\346\200\247\345\212\240\350\275\275.md" index abb703806a..8a6ab56255 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/32.SpringBoot\344\271\213\345\261\236\346\200\247\345\212\240\350\275\275.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/32.SpringBoot\344\271\213\345\261\236\346\200\247\345\212\240\350\275\275.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之属性加载详解 date: 2019-01-10 11:55:54 +order: 32 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/33.SpringBoot\344\271\213Profile.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/33.SpringBoot\344\271\213Profile.md" index 738b668ca9..667df7385c 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/33.SpringBoot\344\271\213Profile.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/33.SpringBoot\344\271\213Profile.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之 Profile date: 2019-11-18 14:55:01 +order: 33 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/README.md" index 0d6e044069..544eb1c101 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/01.Spring\346\240\270\345\277\203/README.md" @@ -13,6 +13,7 @@ tags: - SpringBoot permalink: /pages/5e7c20/ hidden: true +index: false --- # Spring 核心 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/01.Spring\344\271\213\346\225\260\346\215\256\346\272\220.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/01.Spring\344\271\213\346\225\260\346\215\256\346\272\220.md" index 624168ad92..5d01d70337 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/01.Spring\344\271\213\346\225\260\346\215\256\346\272\220.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/01.Spring\344\271\213\346\225\260\346\215\256\346\272\220.md" @@ -1,6 +1,7 @@ --- title: Spring 之数据源 date: 2017-10-20 09:27:55 +order: 01 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/02.Spring\344\271\213JDBC.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/02.Spring\344\271\213JDBC.md" index b7fb50dffe..e199039287 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/02.Spring\344\271\213JDBC.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/02.Spring\344\271\213JDBC.md" @@ -1,6 +1,7 @@ --- title: Spring 之 JDBC date: 2019-02-18 14:33:55 +order: 02 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/03.Spring\344\271\213\344\272\213\345\212\241.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/03.Spring\344\271\213\344\272\213\345\212\241.md" index 137e3c4456..5a9b29fec2 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/03.Spring\344\271\213\344\272\213\345\212\241.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/03.Spring\344\271\213\344\272\213\345\212\241.md" @@ -1,6 +1,7 @@ --- title: Spring 之事务 date: 2022-09-22 07:46:49 +order: 03 categories: - Java - 框架 @@ -66,7 +67,7 @@ Spring 框架为事务管理提供了一致的抽象,具有以下好处: Spring 事务抽象的关键是事务策略的概念。事务策略由 `TransactionManager` 定义,特别是用于命令式事务管理的 `org.springframework.transaction.PlatformTransactionManager` 接口和用于响应式事务管理的 `org.springframework.transaction.ReactiveTransactionManager` 接口。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220922073737.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220922073737.png) #### PlatformTransactionManager @@ -590,7 +591,7 @@ Spring 的 `TransactionInterceptor` 为命令式和响应式编程模型提供 下图显示了在事务代理上调用方法的概念视图: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220927093737.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220927093737.png) ### 声明式事务示例 @@ -1086,7 +1087,7 @@ public class TransactionalService { 在 Spring 管理的事务中,请注意物理事务和逻辑事务之间的差异,以及传播设置如何应用于这种差异。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220928114544.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220928114544.png) `PROPAGATION_REQUIRED` 强制执行物理事务,如果尚不存在事务,则在当前范围的本地执行或参与更大范围定义的现有“外部”事务。 这是同一线程内的常见调用堆栈安排中的一个很好的默认设置(例如,委托给多个存储库方法的服务外观,其中所有底层资源都必须参与服务级事务)。 @@ -1094,7 +1095,7 @@ public class TransactionalService { 但是,在内部事务范围设置了仅回滚标记的情况下,外部事务尚未决定回滚本身,因此回滚(由内部事务范围静默触发)是意外的。此时会引发相应的 `UnexpectedRollbackException`。这是预期的行为,因此事务的调用者永远不会被误导以为执行了提交,而实际上并没有执行。因此,如果内部事务(外部调用者不知道)默默地将事务标记为仅回滚,外部调用者仍会调用提交。外部调用者需要接收 `UnexpectedRollbackException` 以清楚地指示执行了回滚。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220928115243.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220928115243.png) PROPAGATION_REQUIRES_NEW 与 PROPAGATION_REQUIRED 相比,始终为每个受影响的事务范围使用独立的物理事务,从不参与外部范围的现有事务。 在这种安排下,底层资源事务是不同的,因此可以独立提交或回滚,外部事务不受内部事务回滚状态的影响,内部事务的锁在完成后立即释放。 这样一个独立的内部事务也可以声明自己的隔离级别、超时和只读设置,而不是继承外部事务的特性。 @@ -1110,7 +1111,7 @@ ErrorCode 定义(sql-error-codes.xml 文件) ## Spring 事务最佳实践 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200805171418.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200805171418.png) ### Spring 事务未生效 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/04.Spring\344\271\213JPA.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/04.Spring\344\271\213JPA.md" index e4092be548..8d36278912 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/04.Spring\344\271\213JPA.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/04.Spring\344\271\213JPA.md" @@ -1,6 +1,7 @@ --- title: Spring 之 JPA date: 2019-02-18 14:33:55 +order: 04 categories: - Java - 框架 @@ -599,7 +600,7 @@ Spring Data 翻页查询总是返回 Page 对象,Page 对象提供了以下常 ## 核心 API -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230123160810.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230123160810.png) ## 参考资料 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/10.Spring\351\233\206\346\210\220Mybatis.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/10.Spring\351\233\206\346\210\220Mybatis.md" index 02ce026915..d20f39b648 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/10.Spring\351\233\206\346\210\220Mybatis.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/10.Spring\351\233\206\346\210\220Mybatis.md" @@ -1,6 +1,7 @@ --- title: Spring 集成 Mybatis date: 2019-05-09 17:09:25 +order: 10 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/20.SpringData\347\273\274\345\220\210.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/20.SpringData\347\273\274\345\220\210.md" index 720ba5c174..d4120edf29 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/20.SpringData\347\273\274\345\220\210.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/20.SpringData\347\273\274\345\220\210.md" @@ -1,6 +1,7 @@ --- title: Spring Data 综合 date: 2023-02-08 09:10:35 +order: 20 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/21.Spring\350\256\277\351\227\256Redis.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/21.Spring\350\256\277\351\227\256Redis.md" index a0ba2ccc0e..c8bf04e3a8 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/21.Spring\350\256\277\351\227\256Redis.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/21.Spring\350\256\277\351\227\256Redis.md" @@ -1,6 +1,7 @@ --- title: Spring 访问 Redis date: 2023-01-31 20:54:42 +order: 21 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/22.Spring\350\256\277\351\227\256MongoDB.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/22.Spring\350\256\277\351\227\256MongoDB.md" index 1d2e538ffd..03ddf78c7b 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/22.Spring\350\256\277\351\227\256MongoDB.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/22.Spring\350\256\277\351\227\256MongoDB.md" @@ -1,6 +1,7 @@ --- title: Spring 访问 MongoDB date: 2018-12-15 17:29:36 +order: 22 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/23.Spring\350\256\277\351\227\256Elasticsearch.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/23.Spring\350\256\277\351\227\256Elasticsearch.md" index e0dd12b8f3..695ae7ebba 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/23.Spring\350\256\277\351\227\256Elasticsearch.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/23.Spring\350\256\277\351\227\256Elasticsearch.md" @@ -1,6 +1,7 @@ --- title: Spring 访问 Elasticsearch date: 2018-12-25 14:06:36 +order: 23 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/README.md" index 68481b826a..a079aa0219 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/02.Spring\346\225\260\346\215\256/README.md" @@ -14,6 +14,7 @@ tags: - 数据库 permalink: /pages/b912d1/ hidden: true +index: false --- # Spring 数据篇 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/01.SpringWebMvc.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/01.SpringWebMvc.md" index ac57075107..3e8d4b44e6 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/01.SpringWebMvc.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/01.SpringWebMvc.md" @@ -1,6 +1,7 @@ --- title: spring-mvc date: 2017-11-08 16:53:27 +order: 01 categories: - Java - 框架 @@ -20,10 +21,10 @@ permalink: /pages/65351b/ Spring MVC 的工作流程可以用一幅图来说明: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/web/spring-dispatcher-servlet.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/web/spring-dispatcher-servlet.png) 1. 向服务器发送 HTTP 请求,请求被前端控制器 `DispatcherServlet` 捕获。 -2. `DispatcherServlet` 根据 **-servlet.xml** 中的配置对请求的 URL 进行解析,得到请求资源标识符(URI)。然后根据该 URI,调用 `HandlerMapping` 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以`HandlerExecutionChain` 对象的形式返回。 +2. `DispatcherServlet` 根据 **`-servlet.xml`** 中的配置对请求的 URL 进行解析,得到请求资源标识符(URI)。然后根据该 URI,调用 `HandlerMapping` 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以`HandlerExecutionChain` 对象的形式返回。 3. `DispatcherServlet` 根据获得的`Handler`,选择一个合适的 `HandlerAdapter`。(附注:如果成功获得`HandlerAdapter`后,此时将开始执行拦截器的 preHandler(...)方法)。 4. 提取`Request`中的模型数据,填充`Handler`入参,开始执行`Handler`(`Controller`)。 在填充`Handler`的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作: - HttpMessageConveter: 将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的响应信息。 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/21.SpringBoot\344\271\213\345\272\224\347\224\250EasyUI.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/21.SpringBoot\344\271\213\345\272\224\347\224\250EasyUI.md" index 019acc1fae..ef62068ad7 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/21.SpringBoot\344\271\213\345\272\224\347\224\250EasyUI.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/21.SpringBoot\344\271\213\345\272\224\347\224\250EasyUI.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之应用 EasyUI date: 2019-01-08 17:19:34 +order: 21 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/README.md" index a2d045461f..0d69c4ffb2 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/03.SpringWeb/README.md" @@ -14,6 +14,7 @@ tags: - Web permalink: /pages/e2586a/ hidden: true +index: false --- # Spring Web diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/01.SpringBoot\344\271\213\345\274\202\346\255\245\350\257\267\346\261\202.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/01.SpringBoot\344\271\213\345\274\202\346\255\245\350\257\267\346\261\202.md" index 3e2a362dfb..605bc8adfe 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/01.SpringBoot\344\271\213\345\274\202\346\255\245\350\257\267\346\261\202.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/01.SpringBoot\344\271\213\345\274\202\346\255\245\350\257\267\346\261\202.md" @@ -1,6 +1,7 @@ --- title: spring-boot-async date: 2019-11-18 14:55:01 +order: 01 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/02.SpringBoot\344\271\213Json.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/02.SpringBoot\344\271\213Json.md" index 5139889265..0e5da98728 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/02.SpringBoot\344\271\213Json.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/02.SpringBoot\344\271\213Json.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之集成 Json date: 2018-12-30 22:24:16 +order: 02 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/03.SpringBoot\344\271\213\351\202\256\344\273\266.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/03.SpringBoot\344\271\213\351\202\256\344\273\266.md" index ad32746cec..f2c2259cfa 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/03.SpringBoot\344\271\213\351\202\256\344\273\266.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/03.SpringBoot\344\271\213\351\202\256\344\273\266.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之发送邮件 date: 2019-11-20 15:20:44 +order: 03 categories: - Java - 框架 @@ -36,7 +37,7 @@ Spring Framework 提供了一个使用 `JavaMailSender` 接口发送电子邮件 `JavaMailSender` 接口提供的 API 如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20190110111102.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20190110111102.png) ## 配置 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/README.md" index 48d4a25d47..32a77f97aa 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/04.SpringIO/README.md" @@ -14,6 +14,7 @@ tags: - IO permalink: /pages/56581b/ hidden: true +index: false --- # Spring IO diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/01.Spring\351\233\206\346\210\220\347\274\223\345\255\230.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/01.Spring\351\233\206\346\210\220\347\274\223\345\255\230.md" index f953857f64..58c1563200 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/01.Spring\351\233\206\346\210\220\347\274\223\345\255\230.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/01.Spring\351\233\206\346\210\220\347\274\223\345\255\230.md" @@ -1,6 +1,7 @@ --- title: Spring集成缓存 date: 2017-11-08 16:53:27 +order: 01 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/02.Spring\351\233\206\346\210\220\350\260\203\345\272\246\345\231\250.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/02.Spring\351\233\206\346\210\220\350\260\203\345\272\246\345\231\250.md" index 3b4ff84181..2f460e46df 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/02.Spring\351\233\206\346\210\220\350\260\203\345\272\246\345\231\250.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/02.Spring\351\233\206\346\210\220\350\260\203\345\272\246\345\231\250.md" @@ -1,6 +1,7 @@ --- title: Spring 集成调度器 date: 2017-11-08 16:53:27 +order: 02 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/03.Spring\351\233\206\346\210\220Dubbo.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/03.Spring\351\233\206\346\210\220Dubbo.md" index eb98d2f2aa..a36f1168c8 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/03.Spring\351\233\206\346\210\220Dubbo.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/03.Spring\351\233\206\346\210\220Dubbo.md" @@ -1,6 +1,7 @@ --- title: Spring集成Dubbo date: 2017-10-27 17:30:41 +order: 03 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/README.md" index 2ad6e7ea8c..068cd0a824 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/05.Spring\351\233\206\346\210\220/README.md" @@ -14,6 +14,7 @@ tags: - 集成 permalink: /pages/d6025b/ hidden: true +index: false --- # Spring 集成 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/10.Spring\345\256\211\345\205\250/01.SpringBoot\344\271\213\345\256\211\345\205\250\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/10.Spring\345\256\211\345\205\250/01.SpringBoot\344\271\213\345\256\211\345\205\250\345\277\253\351\200\237\345\205\245\351\227\250.md" index 846b3f2058..90cd3fd6bf 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/10.Spring\345\256\211\345\205\250/01.SpringBoot\344\271\213\345\256\211\345\205\250\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/10.Spring\345\256\211\345\205\250/01.SpringBoot\344\271\213\345\256\211\345\205\250\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之安全快速入门 date: 2021-05-13 18:21:56 +order: 01 categories: - Java - 框架 @@ -42,4 +43,4 @@ spring.security.user.roles = USER (3)启动应用后,访问任意路径,都会出现以下页面,提示你先执行登录操作。输入配置的用户名、密码(root/root)即可访问应用页面。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20191118150326556.png) \ No newline at end of file +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20191118150326556.png) \ No newline at end of file diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/01.Spring4\345\215\207\347\272\247.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/01.Spring4\345\215\207\347\272\247.md" index c19b56f551..9864760965 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/01.Spring4\345\215\207\347\272\247.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/01.Spring4\345\215\207\347\272\247.md" @@ -1,6 +1,7 @@ --- title: Spring 4 升级踩雷指南 date: 2017-12-15 15:10:32 +order: 01 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/21.SpringBoot\344\271\213banner.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/21.SpringBoot\344\271\213banner.md" index fd516792af..09a1e61c6a 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/21.SpringBoot\344\271\213banner.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/21.SpringBoot\344\271\213banner.md" @@ -1,6 +1,7 @@ --- title: SpringBoot 之 banner 定制 date: 2018-12-21 23:22:44 +order: 21 categories: - Java - 框架 @@ -74,7 +75,7 @@ ${AnsiBackground.WHITE}${AnsiColor.RED}${AnsiStyle.UNDERLINE} 启动应用后,控制台将打印如下 logo: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181221231330.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20181221231330.png) 推荐两个生成字符画的网站,可以将生成的字符串放入这个`banner.txt` 文件: - diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/22.SpringBoot\344\271\213Actuator.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/22.SpringBoot\344\271\213Actuator.md" index 60b13c0003..f49e516c34 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/22.SpringBoot\344\271\213Actuator.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/22.SpringBoot\344\271\213Actuator.md" @@ -1,6 +1,7 @@ --- title: SpringBoot Actuator 快速入门 date: 2022-06-14 20:51:22 +order: 22 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/README.md" index 5ef1101f62..3e0c927578 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/99.Spring\345\205\266\344\273\226/README.md" @@ -13,6 +13,7 @@ tags: - SpringBoot permalink: /pages/6bb8c1/ hidden: true +index: false --- # Spring 其他 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/README.md" index 5bb30e67ec..e19a47d0d9 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/01.Spring/README.md" @@ -12,6 +12,7 @@ tags: - SpringBoot permalink: /pages/a1a3d3/ hidden: true +index: false --- # SPRING-TUTORIAL @@ -152,4 +153,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/01.Mybatis\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/01.Mybatis\345\277\253\351\200\237\345\205\245\351\227\250.md" index 7696bf11cc..c6bbb2789b 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/01.Mybatis\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/01.Mybatis\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Mybatis快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 框架 @@ -17,11 +18,11 @@ permalink: /pages/d4e6ee/ > MyBatis 的前身就是 iBatis ,是一个作用在数据持久层的对象关系映射(Object Relational Mapping,简称 ORM)框架。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716162305.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716162305.png) ## Mybatis 简介 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210510164925.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210510164925.png) ### 什么是 MyBatis @@ -252,7 +253,7 @@ public class MybatisDemo { ## Mybatis xml 配置 -> 配置的详细内容请参考:「 [Mybatis 官方文档之配置](http://www.mybatis.org/mybatis-3/zh/configuration.html) 」 。 +> 配置的详细内容请参考:“ [Mybatis 官方文档之配置](http://www.mybatis.org/mybatis-3/zh/configuration.html) ” 。 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。主要配置项有以下: @@ -271,7 +272,7 @@ MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性 ## Mybatis xml 映射器 -> SQL XML 映射文件详细内容请参考:「 [Mybatis 官方文档之 XML 映射文件](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) 」。 +> SQL XML 映射文件详细内容请参考:“ [Mybatis 官方文档之 XML 映射文件](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html) ”。 XML 映射文件只有几个顶级元素: @@ -392,4 +393,4 @@ public class ExamplePlugin implements Interceptor { - [mybatis 源码中文注释](https://github.com/tuguangquan/mybatis) - [MyBatis Generator 详解](https://blog.csdn.net/isea533/article/details/42102297) - [Mybatis 常见面试题](https://juejin.im/post/5aa646cdf265da237e095da1) - - [Mybatis 中强大的 resultMap](https://juejin.im/post/5cee8b61e51d455d88219ea4) \ No newline at end of file + - [Mybatis 中强大的 resultMap](https://juejin.im/post/5cee8b61e51d455d88219ea4) diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/02.Mybatis\345\216\237\347\220\206.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/02.Mybatis\345\216\237\347\220\206.md" index 135d6f3936..bd8b3760af 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/02.Mybatis\345\216\237\347\220\206.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/02.Mybatis\345\216\237\347\220\206.md" @@ -1,6 +1,7 @@ --- title: Mybatis原理 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 框架 @@ -228,7 +229,7 @@ public class MybatisDemo { ## Mybatis 生命周期 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210510113446.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210510113446.png) ### SqlSessionFactoryBuilder @@ -238,7 +239,7 @@ public class MybatisDemo { `Configuration` 类包含了对一个 `SqlSessionFactory` 实例你可能关心的所有内容。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210508173040.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210508173040.png) `SqlSessionFactoryBuilder` 应用了建造者设计模式,它有五个 `build` 方法,允许你通过不同的资源创建 `SqlSessionFactory` 实例。 @@ -260,7 +261,7 @@ SqlSessionFactory build(Configuration config) **`SqlSessionFactory` 负责创建 `SqlSession` 实例。** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210510105641.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210510105641.png) `SqlSessionFactory` 应用了工厂设计模式,它提供了一组方法,用于创建 SqlSession 实例。 @@ -299,11 +300,11 @@ Configuration getConfiguration(); **Mybatis 的主要 Java 接口就是 `SqlSession`。它包含了所有执行语句,获取映射器和管理事务等方法。** -> 详细内容可以参考:「 [Mybatis 官方文档之 SqlSessions](http://www.Mybatis.org/Mybatis-3/zh/java-api.html#sqlSessions) 」 。 +> 详细内容可以参考:“ [Mybatis 官方文档之 SqlSessions](http://www.Mybatis.org/Mybatis-3/zh/java-api.html#sqlSessions) ” 。 SqlSession 类的方法可以按照下图进行大致分类: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210510110638.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210510110638.png) #### SqlSession 生命周期 @@ -337,7 +338,7 @@ Mybatis 会根据相应的接口声明的方法信息,通过动态代理机制 下面的示例展示了一些方法签名以及它们是如何映射到 `SqlSession` 上的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210512111723.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210512111723.png) > **注意** > @@ -382,7 +383,7 @@ Mybatis 支持诸如 `@Insert`、`@Update`、`@Delete`、`@Select`、`@Result` 这些组件的架构层次如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210512114852.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210512114852.png) ### 配置层 @@ -436,13 +437,13 @@ Mybatis 和数据库的交互有两种方式: - 如果开启了二级缓存,`SqlSession` 会先使用 `CachingExecutor` 对象来处理查询请求。`CachingExecutor` 会在二级缓存中查看是否有匹配的数据,如果匹配,则直接返回缓存结果;如果缓存中没有,再交给真正的 `Executor` 对象来完成查询,之后 `CachingExecutor` 会将真正 `Executor` 返回的查询结果放置到缓存中,然后在返回给用户。 - 二级缓存的生命周期是应用级别的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210512185709.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210512185709.png) ## SqlSession 内部工作机制 从前文,我们已经了解了,Mybatis 封装了对数据库的访问,把对数据库的会话和事务控制放到了 SqlSession 对象中。那么具体是如何工作的呢?接下来,我们通过源码解读来进行分析。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210512173437.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210512173437.png) `SqlSession` 对于 insert、update、delete、select 的内部处理机制基本上大同小异。所以,接下来,我会以一次完整的 select 查询流程为例讲解 `SqlSession` 内部的工作机制。相信读者如果理解了 select 的处理流程,对于其他 CRUD 操作也能做到一通百通。 @@ -458,7 +459,7 @@ Mybatis 和数据库的交互有两种方式: Executor 即执行器,它负责生成动态 SQL 以及管理缓存。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210512150000.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210512150000.png) - `Executor` 即执行器接口。 - `BaseExecutor` 是 `Executor` 的抽象类,它采用了模板方法设计模式,内置了一些共性方法,而将定制化方法留给子类去实现。 @@ -473,7 +474,7 @@ Executor 即执行器,它负责生成动态 SQL 以及管理缓存。 `StatementHandler` 的家族成员: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210512160243.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210512160243.png) - `StatementHandler` 是接口; - `BaseStatementHandler` 是实现 `StatementHandler` 的抽象类,内置一些共性方法; @@ -571,7 +572,7 @@ Mybatis 所有的配置信息都维持在 `Configuration` 对象之中。中维 `MappedStatement` 维护了一个 Mapper 方法的元数据信息,其数据组织可以参考下面的 debug 截图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210511150650.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210511150650.png) > 小结: > @@ -859,4 +860,4 @@ public List handleResultSets(Statement stmt) throws SQLException { - **文章** - [深入理解 Mybatis 原理](https://blog.csdn.net/luanlouis/article/details/40422941) - [Mybatis 源码中文注释](https://github.com/tuguangquan/Mybatis) - - [Mybatis 中强大的 resultMap](https://juejin.im/post/5cee8b61e51d455d88219ea4) \ No newline at end of file + - [Mybatis 中强大的 resultMap](https://juejin.im/post/5cee8b61e51d455d88219ea4) diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/README.md" index 542853eeb1..1cb04ab89b 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/11.ORM/README.md" @@ -11,6 +11,7 @@ tags: - ORM permalink: /pages/fe879a/ hidden: true +index: false --- # Java ORM 框架 @@ -19,7 +20,7 @@ hidden: true > Mybatis 的前身就是 iBatis ,是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。本文以一个 Mybatis 完整示例为切入点,结合 Mybatis 底层源码分析,图文并茂的讲解 Mybatis 的核心工作机制。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210522101005.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210522101005.png) ### [Mybatis 快速入门](01.Mybatis快速入门.md) @@ -46,4 +47,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/01.Shiro.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/01.Shiro.md" index e143690ebc..70d8f3dda2 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/01.Shiro.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/01.Shiro.md" @@ -1,6 +1,7 @@ --- title: Shiro 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 框架 @@ -22,7 +23,7 @@ permalink: /pages/3295c4/ ### Shiro 特性

- +

核心功能: @@ -46,7 +47,7 @@ permalink: /pages/3295c4/ ### Shiro 架构概述

- +

- **Subject** - **主题**。它代表当前用户,`Subject` 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它——当前和软件交互的任何事件。`Subject` 是 Shiro 的入口。 @@ -61,7 +62,7 @@ permalink: /pages/3295c4/ `SecurityManager` 是 Shiro 框架核心中的核心,它相当于 Shiro 的总指挥,负责调度所有行为,包括:认证、授权、获取安全数据(调用 `Realm`)、会话管理等。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/standalone/security/shiro/ShiroArchitecture.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/standalone/security/shiro/ShiroArchitecture.png) `SecurityManager` 聚合了以下组件: @@ -126,7 +127,7 @@ currentUser.logout(); ### 认证流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200317092427.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200317092427.png) 1. 应用程序代码调用 `Subject.login` 方法,传入构造的 `AuthenticationToken` 实例,该实例代表最终用户的 `Principals` 和 `Credentials`。 @@ -280,7 +281,7 @@ public void updateAccount(Account userAccount) { ### 授权流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200317092618.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200317092618.png) 1. 应用程序或框架代码调用任何 `Subject` 的 `hasRole*`,`checkRole*`,`isPermitted*` 或 `checkPermission*` 方法,并传入所需的权限或角色。 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/02.SpringSecurity.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/02.SpringSecurity.md" index dc8361c214..3434c2dd27 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/02.SpringSecurity.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/12.\345\256\211\345\205\250/02.SpringSecurity.md" @@ -1,6 +1,7 @@ --- title: Spring Security 快速入门 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 框架 @@ -69,7 +70,7 @@ try { Spring Security 框架中的认证数据模型如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200331115710.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200331115710.png) - `Authentication` - 认证信息实体。 - `principal` - 用户标识。如:用户名、账户名等。通常是 `UserDetails` 的实例(后面详细讲解)。 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/13.IO/01.Netty.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/13.IO/01.Netty.md" index 9f455da42c..8bf182aac1 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/13.IO/01.Netty.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/13.IO/01.Netty.md" @@ -1,6 +1,7 @@ --- title: Netty 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 框架 diff --git "a/source/_posts/01.Java/13.\346\241\206\346\236\266/README.md" "b/source/_posts/01.Java/13.\346\241\206\346\236\266/README.md" index 9c0993377b..7843b3d3eb 100644 --- "a/source/_posts/01.Java/13.\346\241\206\346\236\266/README.md" +++ "b/source/_posts/01.Java/13.\346\241\206\346\236\266/README.md" @@ -9,6 +9,7 @@ tags: - 框架 permalink: /pages/e373d7/ hidden: true +index: false --- # Java 框架 @@ -117,4 +118,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/01.\347\274\223\345\255\230\351\235\242\350\257\225\351\242\230.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/01.\347\274\223\345\255\230\351\235\242\350\257\225\351\242\230.md" deleted file mode 100644 index 94717b82c2..0000000000 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/01.\347\274\223\345\255\230\351\235\242\350\257\225\351\242\230.md" +++ /dev/null @@ -1,586 +0,0 @@ ---- -title: 缓存夺命连环问 -date: 2022-02-17 22:34:30 -categories: - - Java - - 中间件 - - 缓存 -tags: - - Java - - 中间件 - - 缓存 - - 面试 -permalink: /pages/4b3e5d/ ---- - -# 缓存夺命连环问 - -## 为什么要用缓存? - -用缓存,主要有两个用途:**高性能**、**高并发**。 - -### 高性能 - -假设这么个场景,你有个操作,一个请求过来,吭哧吭哧你各种乱七八糟操作 mysql,半天查出来一个结果,耗时 600ms。但是这个结果可能接下来几个小时都不会变了,或者变了也可以不用立即反馈给用户。那么此时咋办? - -缓存啊,折腾 600ms 查出来的结果,扔缓存里,一个 key 对应一个 value,下次再有人查,别走 mysql 折腾 600ms 了,直接从缓存里,通过一个 key 查出来一个 value,2ms 搞定。性能提升 300 倍。 - -就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好。 - -### 高并发 - -mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql 单机支撑到 `2000QPS` 也开始容易报警了。 - -所以要是你有个系统,高峰期一秒钟过来的请求有 1 万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。 - -> 缓存是走内存的,内存天然就支撑高并发。 - -## 用了缓存之后会有什么不良后果? - -常见的缓存问题有以下几个: - -- [缓存与数据库双写不一致](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-consistence.md) -- [缓存雪崩、缓存穿透](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) -- [缓存并发竞争](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-cas.md) - -后面再详细说明。 - -## redis 和 memcached 有啥区别? - -redis 支持复杂的数据结构 - -redis 相比 memcached 来说,拥有[更多的数据结构](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。 - -redis 原生支持集群模式 - -在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。 - -性能对比 - -由于 redis 只使用**单核**,而 memcached 可以使用**多核**,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis。虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。 - -## 为啥 redis 单线程模型也能效率这么高? - -### redis 的线程模型 - -redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。 - -文件事件处理器的结构包含 4 个部分: - -- 多个 socket -- IO 多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) - -多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。 - -来看客户端与 redis 的一次通信过程: - -![img](https://github.com/doocs/advanced-java/blob/master/images/redis-single-thread-model.png) - -要明白,通信是通过 socket 来完成的,不懂的同学可以先去看一看 socket 网络编程。 - -首先,redis 服务端进程初始化的时候,会将 server socket 的 `AE_READABLE` 事件与连接应答处理器关联。 - -客户端 socket01 向 redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个 `AE_READABLE` 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。文件事件分派器从队列中获取 socket,交给**连接应答处理器**。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 `AE_READABLE` 事件与命令请求处理器关联。 - -假设此时客户端发送了一个 `set key value` 请求,此时 redis 中的 socket01 会产生 `AE_READABLE` 事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 `AE_READABLE` 事件,由于前面 socket01 的 `AE_READABLE` 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 `key value` 并在自己内存中完成 `key value` 的设置。操作完成后,它会将 socket01 的 `AE_WRITABLE` 事件与命令回复处理器关联。 - -如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 `AE_WRITABLE` 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 `ok`,之后解除 socket01 的 `AE_WRITABLE` 事件与命令回复处理器的关联。 - -这样便完成了一次通信。 - -### 为啥 redis 单线程模型也能效率这么高? - -- 纯内存操作。 -- 核心是基于非阻塞的 IO 多路复用机制。 -- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 -- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 - -## Redis 有哪些数据类型 - -redis 主要有以下几种数据类型: - -- string -- hash -- list -- set -- sorted set - -### string - -这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。 - -``` -set college szu -``` - -### hash - -这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是**这个对象没嵌套其他的对象**)给缓存在 redis 里,然后每次读写缓存的时候,可以就操作 hash 里的**某个字段**。 - -```bash -hset person name bingo -hset person age 20 -hset person id 1 -hget person name -person = { - "name": "bingo", - "age": 20, - "id": 1 -} -``` - -### list - -list 是有序列表,这个可以玩儿出很多花样。 - -比如可以通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。 - -比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。 - -``` -# 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。 -lrange mylist 0 -1 -``` - -比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。 - -``` -lpush mylist 1 -lpush mylist 2 -lpush mylist 3 4 5 - -# 1 -rpop mylist -``` - -### set - -set 是无序集合,自动去重。 - -直接基于 set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 jvm 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于 redis 进行全局的 set 去重。 - -可以基于 set 玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧。 - -把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。 - -``` -#-------操作一个set------- -# 添加元素 -sadd mySet 1 - -# 查看全部元素 -smembers mySet - -# 判断是否包含某个值 -sismember mySet 3 - -# 删除某个/些元素 -srem mySet 1 -srem mySet 2 4 - -# 查看元素个数 -scard mySet - -# 随机删除一个元素 -spop mySet - -#-------操作多个set------- -# 将一个set的元素移动到另外一个set -smove yourSet mySet 2 - -# 求两set的交集 -sinter yourSet mySet - -# 求两set的并集 -sunion yourSet mySet - -# 求在yourSet中而不在mySet中的元素 -sdiff yourSet mySet -``` - -### sorted set - -sorted set 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。 - -``` -zadd board 85 zhangsan -zadd board 72 lisi -zadd board 96 wangwu -zadd board 63 zhaoliu - -# 获取排名前三的用户(默认是升序,所以需要 rev 改为降序) -zrevrange board 0 3 - -# 获取某用户的排名 -zrank board zhaoliu -``` - -## 如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么? - -如果你用 redis 缓存技术的话,肯定要考虑如何用 redis 来加多台机器,保证 redis 是高并发的,还有就是如何让 redis 保证自己不是挂掉以后就直接死掉了,即 redis 高可用。 - -由于此节内容较多,因此,会分为两个小节进行讲解。 - -- [redis 主从架构](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-master-slave.md) -- [redis 基于哨兵实现高可用](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-sentinel.md) - -redis 实现**高并发**主要依靠**主从架构**,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。 - -如果想要在实现高并发的同时,容纳大量的数据,那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒几十万的读写并发。 - -redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。 - -## redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? - -### redis 过期策略 - -redis 过期策略是:**定期删除+惰性删除**。 - -所谓**定期删除**,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。 - -假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的**灾难**。实际上 redis 是每隔 100ms **随机抽取**一些 key 来检查和删除的。 - -但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 - -> 获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。 - -但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整? - -答案是:**走内存淘汰机制**。 - -### 内存淘汰机制 - -redis 内存淘汰机制有以下几个: - -- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。 -- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。 -- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。 -- volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。 -- volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。 -- volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。 - -### 手写一个 LRU 算法 - -你可以现场手写最原始的 LRU 算法,那个代码量太大了,似乎不太现实。 - -不求自己纯手工从底层开始打造出自己的 LRU,但是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。 - -```java -class LRUCache extends LinkedHashMap { - private final int CACHE_SIZE; - - /** - * 传递进来最多能缓存多少数据 - * - * @param cacheSize 缓存大小 - */ - public LRUCache(int cacheSize) { - // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。 - super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); - CACHE_SIZE = cacheSize; - } - - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 - return size() > CACHE_SIZE; - } -} -``` - -## redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗? - -在前几年,redis 如果要搞几个节点,每个节点存储一部分的数据,得**借助一些中间件**来实现,比如说有 `codis`,或者 `twemproxy`,都有。有一些 redis 中间件,你读写 redis 中间件,redis 中间件负责将你的数据分布式存储在多台机器上的 redis 实例中。 - -这两年,redis 不断在发展,redis 也不断有新的版本,现在的 redis 集群模式,可以做到在多台机器上,部署多个 redis 实例,每个实例存储一部分的数据,同时每个 redis 主实例可以挂 redis 从实例,自动确保说,如果 redis 主实例挂了,会自动切换到 redis 从实例上来。 - -现在 redis 的新版本,大家都是用 redis cluster 的,也就是 redis 原生支持的 redis 集群模式,那么面试官肯定会就 redis cluster 对你来个几连炮。要是你没用过 redis cluster,正常,以前很多人用 codis 之类的客户端来支持集群,但是起码你得研究一下 redis cluster 吧。 - -如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个 G,单机就足够了,可以使用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 sentinel 集群去保证 redis 主从架构的高可用性。 - -redis cluster,主要是针对**海量数据+高并发+高可用**的场景。redis cluster 支撑 N 个 redis master node,每个 master node 都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。 - -### 面试题剖析 - -### redis cluster 介绍 - -- 自动将数据进行分片,每个 master 上放一部分数据 -- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 - -在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加 1w 的端口号,比如 16379。 - -16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,`gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。 - -### 节点间的内部通信机制 - -#### 基本通信原理 - -集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。 - -**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/zookeeper-centralized-storage.png) - -redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/redis-gossip.png) - -**集中式**的**好处**在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;**不好**在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。 - -gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。 - -- 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong`。 -- 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。 - -#### gossip 协议 - -gossip 协议包含多种消息,包含 `ping`,`pong`,`meet`,`fail` 等等。 - -- meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。 - -``` -redis-trib.rb add-node -``` - -其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。 - -- ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。 -- pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。 -- fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。 - -#### ping 消息深入 - -ping 时要携带一些元数据,如果很频繁,可能会加重网络负担。 - -每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 `cluster_node_timeout / 2`,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 `cluster_node_timeout` 可以调节,如果调得比较大,那么会降低 ping 的频率。 - -每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 `3` 个其它节点的信息,最多包含 `总节点数减 2` 个其它节点的信息。 - -### 分布式寻址算法 - -- hash 算法(大量缓存重建) -- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡) -- redis cluster 的 hash slot 算法 - -#### hash 算法 - -来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致**大部分的请求过来,全部无法拿到有效的缓存**,导致大量的流量涌入数据库。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/hash.png) - -#### 一致性 hash 算法 - -一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。 - -来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环**顺时针“行走”**,遇到的第一个 master 节点就是 key 所在位置。 - -在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。 - -燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成**缓存热点**的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/consistent-hashing-algorithm.png) - -#### redis cluster 的 hash slot 算法 - -redis cluster 有固定的 `16384` 个 hash slot,对每个 `key` 计算 `CRC16` 值,然后对 `16384` 取模,可以获取 key 对应的 hash slot。 - -redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 `hash tag` 来实现。 - -任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/hash-slot.png) - -### redis cluster 的高可用与主备切换原理 - -redis cluster 的高可用的原理,几乎跟哨兵是类似的。 - -#### 判断节点宕机 - -如果一个节点认为另外一个节点宕机,那么就是 `pfail`,**主观宕机**。如果多个节点都认为另外一个节点宕机了,那么就是 `fail`,**客观宕机**,跟哨兵的原理几乎一样,sdown,odown。 - -在 `cluster-node-timeout` 内,某个节点一直没有返回 `pong`,那么就被认为 `pfail`。 - -如果一个节点认为某个节点 `pfail` 了,那么会在 `gossip ping` 消息中,`ping` 给其他节点,如果**超过半数**的节点都认为 `pfail` 了,那么就会变成 `fail`。 - -#### 从节点过滤 - -对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node。 - -检查每个 slave node 与 master node 断开连接的时间,如果超过了 `cluster-node-timeout * cluster-slave-validity-factor`,那么就**没有资格**切换成 `master`。 - -#### 从节点选举 - -每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。 - -所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node`(N/2 + 1)`都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。 - -从节点执行主备切换,从节点切换为主节点。 - -#### 与哨兵比较 - -整个流程跟哨兵相比,非常类似,所以说,redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。 - -## 如何保证缓存与数据库的双写一致性? - -一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统**不是严格要求** “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:**读请求和写请求串行化**,串到一个**内存队列**里去。 - -串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。 - -### Cache Aside Pattern - -最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。 - -- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 -- 更新的时候,**先更新数据库,然后再删除缓存**。 - -**为什么是删除缓存,而不是更新缓存?** - -原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。 - -比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。 - -另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于**比较复杂的缓存数据计算的场景**,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,**这个缓存到底会不会被频繁访问到?** - -举个栗子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次,有**大量的冷数据**。实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。**用到缓存才去算缓存。** - -其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。 - -### 最初级的缓存不一致问题及解决方案 - -问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/redis-junior-inconsistent.png) - -解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。 - -### 比较复杂的数据不一致问题分析 - -数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,**查到了修改前的旧数据**,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了... - -**为什么上亿流量高并发场景下,缓存会出现这个问题?** - -只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就 1 万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就**可能会出现上述的数据库+缓存不一致的情况**。 - -**解决方案如下:** - -更新数据的时候,根据**数据的唯一标识**,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。 - -一个队列对应一个工作线程,每个工作线程**串行**拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,没有读到缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。 - -这里有一个**优化点**,一个队列中,其实**多个更新缓存请求串在一起是没意义的**,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。 - -待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。 - -如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。 - -高并发的场景下,该解决方案要注意的问题: - -- 读请求长时阻塞 - -由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。 - -该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。 - -另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每隔库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 \* 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。 - -一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。 - -**如果一个内存队列中可能积压的更新操作特别多**,那么你就要**加机器**,让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少。 - -其实根据之前的项目经验,一般来说,数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的。像这种针对读高并发、读缓存架构的项目,一般来说写请求是非常少的,每秒的 QPS 能到几百就不错了。 - -我们来**实际粗略测算一下**。 - -如果一秒有 500 的写操作,如果分成 5 个时间片,每 200ms 就 100 个写操作,放到 20 个内存队列中,每个内存队列,可能就积压 5 个写操作。每个写操作性能测试后,一般是在 20ms 左右就完成,那么针对每个内存队列的数据的读请求,也就最多 hang 一会儿,200ms 以内肯定能返回了。 - -经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。 - -- 读请求并发量过高 - -这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。 - -但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。 - -- 多服务实例部署的请求路由 - -可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。 - -比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。 - -- 热点商品的路由问题,导致请求的倾斜 - -万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。 - -## 了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透? - -### 缓存雪崩 - -对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 - -这就是缓存雪崩。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-avalanche.png) - -大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3\~4 点,公司损失了几千万。 - -缓存雪崩的事前事中事后的解决方案如下。 - -- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。 -- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 -- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-avalanche-solution.png) - -用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。 - -限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?**走降级**!可以返回一些默认的值,或者友情提示,或者空白的值。 - -好处: - -- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 -- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 -- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。 - -### 缓存穿透 - -对于系统 A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。 - -黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。 - -举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/redis-caching-penetration.png) - -解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 - -### 缓存击穿 - -缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。 - -解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。 - -## redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗? - -某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。 - -![img](https://github.com/doocs/advanced-java/blob/master/images/zookeeper-distributed-lock.png) - -你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。 - -每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。 - -## 生产环境中的 redis 是怎么部署的? - -redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰 qps 可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。 - -机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是 10g 内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。 - -5 台机器对外提供读写,一共有 50g 内存。 - -因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。 - -你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。 - -其实大型的公司,会有基础架构的 team 负责缓存集群的运维。 \ No newline at end of file diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/02.Java\347\274\223\345\255\230\344\270\255\351\227\264\344\273\266.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/02.Java\347\274\223\345\255\230\344\270\255\351\227\264\344\273\266.md" index 467e50e754..9adf2b83c6 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/02.Java\347\274\223\345\255\230\344\270\255\351\227\264\344\273\266.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/02.Java\347\274\223\345\255\230\344\270\255\351\227\264\344\273\266.md" @@ -1,6 +1,7 @@ --- title: Java 缓存中间件 date: 2022-02-17 22:34:30 +order: 02 categories: - Java - 中间件 @@ -22,7 +23,7 @@ permalink: /pages/85460d/ 因此,在很多缓存框架、缓存库中,其 API 都参考了 JSR 107 规范。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200709174139.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200709174139.png) Java Caching 定义了 5 个核心接口 diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/04.Ehcache.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/04.Ehcache.md" index 959e8eb126..dd76153f46 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/04.Ehcache.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/04.Ehcache.md" @@ -1,6 +1,7 @@ --- title: Ehcache 快速入门 date: 2022-02-17 22:34:30 +order: 04 categories: - Java - 中间件 @@ -17,7 +18,7 @@ permalink: /pages/5f7893/ > EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/cache/ehcache-architecture.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/cache/ehcache-architecture.png) ## 一、简介 diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/05.Java\350\277\233\347\250\213\345\206\205\347\274\223\345\255\230.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/05.Java\350\277\233\347\250\213\345\206\205\347\274\223\345\255\230.md" index 9f1aea52ce..53562aa9ba 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/05.Java\350\277\233\347\250\213\345\206\205\347\274\223\345\255\230.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/05.Java\350\277\233\347\250\213\345\206\205\347\274\223\345\255\230.md" @@ -1,6 +1,7 @@ --- title: Java 进程内缓存 date: 2022-02-17 22:34:30 +order: 05 categories: - Java - 中间件 @@ -150,7 +151,7 @@ Caffeine 实现了 W-TinyLFU(**LFU** + **LRU** 算法的变种),其**命中率 ## 五、Ehcache -> 参考:[Ehcache](docs/04.中间件/02.缓存/ehcache.md) +> 参考:[Ehcache](04.Ehcache.md) ## 六、进程内缓存对比 diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/06.Http\347\274\223\345\255\230.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/06.Http\347\274\223\345\255\230.md" index 894207ed8d..b9c26d3455 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/06.Http\347\274\223\345\255\230.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/06.Http\347\274\223\345\255\230.md" @@ -1,6 +1,7 @@ --- title: Http 缓存 date: 2022-02-17 22:34:30 +order: 06 categories: - Java - 中间件 diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/README.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/README.md" index f4f960fe54..b5487f648a 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/README.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/README.md" @@ -11,6 +11,7 @@ tags: - 缓存 permalink: /pages/c4efe9/ hidden: true +index: false --- # Java 缓存 @@ -19,13 +20,11 @@ hidden: true > > 如果想深入学习缓存,建议先了解一下 [缓存基本原理](https://dunwu.github.io/design/distributed/分布式缓存.html),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200710163555.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200710163555.png) ## 📖 内容 -- [缓存面试题](01.缓存面试题.md) - [Java 缓存框架](02.Java缓存中间件.md) -- [Memcached 快速入门](03.Memcached.md) - [Ehcache 快速入门](04.Ehcache.md) - [Java 缓存库](05.Java进程内缓存.md) - [Http 缓存](06.Http缓存.md) @@ -46,4 +45,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/03.\346\265\201\351\207\217\346\216\247\345\210\266/01.Hystrix.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/03.\346\265\201\351\207\217\346\216\247\345\210\266/01.Hystrix.md" index b215faf250..1cb5617ae9 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/03.\346\265\201\351\207\217\346\216\247\345\210\266/01.Hystrix.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/03.\346\265\201\351\207\217\346\216\247\345\210\266/01.Hystrix.md" @@ -1,6 +1,7 @@ --- title: Hystrix 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - Java - 中间件 @@ -43,15 +44,15 @@ Hystrix 官方宣布**不再发布新版本**。 当一切正常时,整体系统如下所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717141615.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717141615.png) 在高并发场景,这些依赖的稳定性与否对系统的影响非常大,但是依赖有很多不可控问题:如网络连接、资源繁忙、服务宕机等。例如:下图中有一个 QPS 为 50 的依赖 I 出现不可用,但是其他依赖服务是可用的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717141749.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717141749.png) 但是,在高并发场景下,当依赖 I 阻塞时,大多数服务器的线程池就出现阻塞(BLOCK)。当这种级联故障愈演愈烈,就可能造成整个线上服务不可用的雪崩效应,如下图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717141859.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717141859.png) Hystrix 就是为了解决这类问题而应运而生。 @@ -69,7 +70,7 @@ Hystrix 具有以下功能: 如果使用 Hystrix 对每个基础依赖服务进行过载保护,则整个系统架构将会类似下图所示,每个依赖项彼此隔离,受到延迟时发生饱和的资源的被限制访问,并包含 fallback 逻辑(用于降级处理),该逻辑决定了在依赖项中发生任何类型的故障时做出对应的处理。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717142842.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717142842.png) ## Hystrix 核心概念 @@ -77,7 +78,7 @@ Hystrix 具有以下功能: 如下图所示,Hystrix 的工作流程大致可以分为 9 个步骤。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717143247.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717143247.png) ### (一)包装命令 diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/99.\345\205\266\344\273\226/01.\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/99.\345\205\266\344\273\226/01.\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" index 7a0320acf9..f947bed49d 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/99.\345\205\266\344\273\226/01.\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/99.\345\205\266\344\273\226/01.\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240.md" @@ -1,6 +1,7 @@ --- title: 数据库连接池 date: 2022-09-21 23:58:06 +order: 01 categories: - Java - 中间件 @@ -24,7 +25,7 @@ permalink: /pages/be5227/ ### 不使用数据库连接池 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220921231353.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220921231353.png) 不使用数据库连接池的**步骤**: @@ -46,7 +47,7 @@ permalink: /pages/be5227/ ### 使用数据库连接池 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220921231500.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220921231500.png) 使用数据库连接池的步骤:只有第一次访问的时候,需要建立连接。 但是之后的访问,均会**复用**之前创建的连接,直接执行 SQL 语句。 diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/README.md" "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/README.md" index 939bb69edc..fd3311e006 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/README.md" +++ "b/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/README.md" @@ -10,6 +10,7 @@ tags: - 中间件 permalink: /pages/fe6d83/ hidden: true +index: false --- # Java 中间件 @@ -22,9 +23,7 @@ hidden: true > > 如果想深入学习缓存,建议先了解一下 [缓存基本原理](https://dunwu.github.io/design/distributed/分布式缓存.html),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 -- [缓存面试题](02.缓存/01.缓存面试题.md) - [Java 缓存框架](02.缓存/02.Java缓存中间件.md) -- [Memcached 快速入门](02.缓存/03.Memcached.md) - [Ehcache 快速入门](02.缓存/04.Ehcache.md) - [Java 进程内缓存](02.缓存/05.Java进程内缓存.md) - [Http 缓存](02.缓存/06.Http缓存.md) @@ -47,4 +46,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git a/source/_posts/01.Java/README.md b/source/_posts/01.Java/README.md index 20ae63dcf1..51ee60ab50 100644 --- a/source/_posts/01.Java/README.md +++ b/source/_posts/01.Java/README.md @@ -7,11 +7,12 @@ tags: - Java permalink: /pages/0d2474/ hidden: true +index: false ---

- logo + logo

@@ -77,7 +78,7 @@ hidden: true #### [Java 容器](01.JavaSE/03.容器) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221175550.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221175550.png) - [Java 容器简介](01.JavaSE/03.容器/01.Java容器简介.md) - 关键词:`Collection`、`泛型`、`Iterable`、`Iterator`、`Comparable`、`Comparator`、`Cloneable`、`fail-fast` - [Java 容器之 List](01.JavaSE/03.容器/02.Java容器之List.md) - 关键词:`List`、`ArrayList`、`LinkedList` @@ -88,7 +89,7 @@ hidden: true #### [Java IO](01.JavaSE/04.IO) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630205329.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630205329.png) - [Java IO 模型](01.JavaSE/04.IO/01.JavaIO模型.md) - 关键词:`InputStream`、`OutputStream`、`Reader`、`Writer`、`阻塞` - [Java NIO](01.JavaSE/04.IO/02.JavaNIO.md) - 关键词:`Channel`、`Buffer`、`Selector`、`非阻塞`、`多路复用` @@ -98,7 +99,7 @@ hidden: true #### [Java 并发](01.JavaSE/05.并发) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200221175827.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200221175827.png) - [Java 并发简介](01.JavaSE/05.并发/01.Java并发简介.md) - 关键词:`进程`、`线程`、`安全性`、`活跃性`、`性能`、`死锁`、`饥饿`、`上下文切换` - [Java 线程基础](01.JavaSE/05.并发/02.Java线程基础.md) - 关键词:`Thread`、`Runnable`、`Callable`、`Future`、`wait`、`notify`、`notifyAll`、`join`、`sleep`、`yeild`、`线程状态`、`线程通信` @@ -113,7 +114,7 @@ hidden: true #### [Java 虚拟机](01.JavaSE/06.JVM) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200628154803.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200628154803.png) - [JVM 体系结构](01.JavaSE/06.JVM/01.JVM体系结构.md) - [JVM 内存区域](01.JavaSE/06.JVM/02.JVM内存区域.md) - 关键词:`程序计数器`、`虚拟机栈`、`本地方法栈`、`堆`、`方法区`、`运行时常量池`、`直接内存`、`OutOfMemoryError`、`StackOverflowError` @@ -322,9 +323,7 @@ hidden: true > > 如果想深入学习缓存,建议先了解一下 [缓存基本原理](https://dunwu.github.io/design/distributed/分布式缓存.html),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 -- [缓存面试题](14.中间件/02.缓存/01.缓存面试题.md) - [Java 缓存中间件](14.中间件/02.缓存/02.Java缓存中间件.md) -- [Memcached 快速入门](14.中间件/02.缓存/03.Memcached.md) - [Ehcache 快速入门](14.中间件/02.缓存/04.Ehcache.md) - [Java 进程内缓存](14.中间件/02.缓存/05.Java进程内缓存.md) - [Http 缓存](14.中间件/02.缓存/06.Http缓存.md) @@ -365,4 +364,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\345\246\202\344\275\225\345\255\246\344\271\240\347\274\226\347\250\213\350\257\255\350\250\200.md" "b/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\345\246\202\344\275\225\345\255\246\344\271\240\347\274\226\347\250\213\350\257\255\350\250\200.md" index 5cd072425d..75c3d34204 100644 --- "a/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\345\246\202\344\275\225\345\255\246\344\271\240\347\274\226\347\250\213\350\257\255\350\250\200.md" +++ "b/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\345\246\202\344\275\225\345\255\246\344\271\240\347\274\226\347\250\213\350\257\255\350\250\200.md" @@ -1,6 +1,7 @@ --- title: 如何学习编程语言 date: 2018-01-05 09:08:00 +order: 01 categories: - 编程 - 编程范式 @@ -29,7 +30,7 @@ permalink: /pages/1d2aa9/ ## 学习编程语言的步骤 -
+
### 基本语法 diff --git "a/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\351\224\231\350\257\257\345\244\204\347\220\206.md" "b/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\351\224\231\350\257\257\345\244\204\347\220\206.md" index fe51766141..6ca3e97c98 100644 --- "a/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\351\224\231\350\257\257\345\244\204\347\220\206.md" +++ "b/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/01.\351\224\231\350\257\257\345\244\204\347\220\206.md" @@ -1,6 +1,7 @@ --- title: 错误处理 date: 2020-08-13 23:32:37 +order: 01 categories: - 编程 - 编程范式 diff --git "a/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/README.md" "b/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/README.md" index 9fb493c7ec..7b89514f7b 100644 --- "a/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/README.md" +++ "b/source/_posts/02.\347\274\226\347\250\213/01.\347\274\226\347\250\213\350\214\203\345\274\217/README.md" @@ -9,6 +9,7 @@ tags: - 编程范式 permalink: /pages/34f6f0/ hidden: true +index: false --- # 编程范式 @@ -25,4 +26,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/01.python.md" "b/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/01.python.md" index a69b72007e..1078a795d5 100644 --- "a/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/01.python.md" +++ "b/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/01.python.md" @@ -1,6 +1,7 @@ --- title: 一篇文章让你掌握 Python date: 2018-06-28 16:52:00 +order: 01 categories: - 编程 - 编程语言 diff --git "a/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/02.shell.md" "b/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/02.shell.md" index ce85350703..9bebc83d75 100644 --- "a/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/02.shell.md" +++ "b/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/02.shell.md" @@ -1,6 +1,7 @@ --- title: 一篇文章让你彻底掌握 Shell date: 2017-11-17 18:16:00 +order: 02 categories: - 编程 - 编程语言 @@ -17,7 +18,7 @@ permalink: /pages/ea6ae1/ > > _本文主要介绍 bash 的语法,对于 linux 指令不做任何介绍_。 > -> 💻 本文的源码已归档到『 [**_linux-tutorial_**](https://github.com/dunwu/linux-tutorial/tree/master/codes/shell/demos)』 +> 💻 本文的源码已归档到“ [**_linux-tutorial_**](https://github.com/dunwu/linux-tutorial/tree/master/codes/shell/demos)” ``` ███████╗██╗ ██╗███████╗██╗ ██╗ @@ -129,7 +130,7 @@ chmod +x /path/to/script.sh #使脚本具有执行权限 这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如: -**💻 『示例源码』** +**💻 “示例源码”** ```shell #!/usr/bin/env bash @@ -163,7 +164,7 @@ shell 语法中,注释是特殊的语句,会被 shell 解释器忽略。 - 单行注释 - 以 `#` 开头,到行尾结束。 - 多行注释 - 以 `:< > `continue`语句用来跳过某次迭代。 -**💻 『示例源码』** +**💻 “示例源码”** ```shell # 查找 10 以内第一个能整除 2 和 3 的正整数 @@ -1456,7 +1457,7 @@ done # Output: 6 ``` -**💻 『示例源码』** +**💻 “示例源码”** ```shell # 打印10以内的奇数 @@ -1492,7 +1493,7 @@ bash 函数定义语法如下: > 3. 函数返回值在调用该函数后通过 `$?` 来获得。 > 4. 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。 -**💻 『示例源码』** +**💻 “示例源码”** ```shell #!/usr/bin/env bash @@ -1558,7 +1559,7 @@ the result is: 100 | `$#` | 不包括`$0`在内的位置参数的个数 | | `$FUNCNAME` | 函数名称(仅在函数内部有值) | -**💻 『示例源码』** +**💻 “示例源码”** ```shell #!/usr/bin/env bash @@ -1618,7 +1619,7 @@ $ ./function-demo2.sh 10 20 | `$-` | 返回 Shell 使用的当前选项,与 set 命令功能相同。 | | `$?` | 函数返回值 | -**💻 『示例源码』** +**💻 “示例源码”** ```shell runner() { @@ -1856,7 +1857,7 @@ $ ./my_script 有时我们值需要 debug 脚本的一部分。这种情况下,使用`set`命令会很方便。这个命令可以启用或禁用选项。使用`-`启用选项,`+`禁用选项: -**💻 『示例源码』** +**💻 “示例源码”** ```shell # 开启 debug @@ -1896,4 +1897,4 @@ printf "\n" - [Runoob Shell 教程](http://www.runoob.com/linux/linux-shell.html) - [shellcheck](https://github.com/koalaman/shellcheck) - 一个静态 shell 脚本分析工具,本质上是 bash/sh/zsh 的 lint。 -最后,Stack Overflow 上 [bash 标签下](https://stackoverflow.com/questions/tagged/bash)有很多你可以学习的问题,当你遇到问题时,也是一个提问的好地方。 \ No newline at end of file +最后,Stack Overflow 上 [bash 标签下](https://stackoverflow.com/questions/tagged/bash)有很多你可以学习的问题,当你遇到问题时,也是一个提问的好地方。 diff --git "a/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/03.scala.md" "b/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/03.scala.md" index d6ff2d4abc..2ca69327be 100644 --- "a/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/03.scala.md" +++ "b/source/_posts/02.\347\274\226\347\250\213/02.\347\274\226\347\250\213\350\257\255\350\250\200/03.scala.md" @@ -1,6 +1,7 @@ --- title: 一篇文章让你彻底掌握 Scala date: 2021-04-14 15:27:00 +order: 03 categories: - 编程 - 编程语言 diff --git "a/source/_posts/02.\347\274\226\347\250\213/README.md" "b/source/_posts/02.\347\274\226\347\250\213/README.md" index 59d10a8de2..155fd79de1 100644 --- "a/source/_posts/02.\347\274\226\347\250\213/README.md" +++ "b/source/_posts/02.\347\274\226\347\250\213/README.md" @@ -7,6 +7,7 @@ tags: - 编程 permalink: /pages/f85bac/ hidden: true +index: false --- # 编程 @@ -25,4 +26,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/00.\345\246\202\344\275\225\350\256\276\350\256\241\347\263\273\347\273\237.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/00.\345\246\202\344\275\225\350\256\276\350\256\241\347\263\273\347\273\237.md" index 2a4c300934..df351d5d59 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/00.\345\246\202\344\275\225\350\256\276\350\256\241\347\263\273\347\273\237.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/00.\345\246\202\344\275\225\350\256\276\350\256\241\347\263\273\347\273\237.md" @@ -1,6 +1,7 @@ --- title: 如何设计系统 date: 2021-11-08 08:15:33 +order: 00 categories: - 设计 - 架构 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/01.\347\263\273\347\273\237\346\236\266\346\236\204\351\235\242\350\257\225.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/01.\347\263\273\347\273\237\346\236\266\346\236\204\351\235\242\350\257\225.md" index 2657319468..09d91f393f 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/01.\347\263\273\347\273\237\346\236\266\346\236\204\351\235\242\350\257\225.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/01.\347\263\273\347\273\237\346\236\266\346\236\204\351\235\242\350\257\225.md" @@ -1,6 +1,7 @@ --- title: 系统架构面试 date: 2020-08-10 10:59:18 +order: 01 categories: - 设计 - 架构 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/02.\347\263\273\347\273\237\346\236\266\346\236\204\346\246\202\350\277\260.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/02.\347\263\273\347\273\237\346\236\266\346\236\204\346\246\202\350\277\260.md" index a9db8025c2..8d34e2806c 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/02.\347\263\273\347\273\237\346\236\266\346\236\204\346\246\202\350\277\260.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/02.\347\263\273\347\273\237\346\236\266\346\236\204\346\246\202\350\277\260.md" @@ -1,12 +1,15 @@ --- title: 系统架构概述 date: 2018-07-05 15:11:00 +order: 02 categories: - 设计 - 架构 - 综合 tags: - 架构 + - 分布式 + - 微服务 permalink: /pages/db2390/ --- @@ -26,7 +29,7 @@ permalink: /pages/db2390/ - **特征**:**应用程序、数据库、文件等所有的资源都在一台服务器上。** - **描述**:通常服务器操作系统使用 linux,应用程序使用 PHP 开发,然后部署在 Apache 上,数据库使用 Mysql,通俗称为 LAMP。汇集各种免费开源软件以及一台廉价服务器就可以开始系统的发展之路了。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200702718.png) ### 应用服务和数据服务分离 @@ -37,7 +40,7 @@ permalink: /pages/db2390/ - 数据库服务器需要快速磁盘检索和数据缓存,因此需要更快的硬盘和更大的内存; - 文件服务器需要存储大量文件,因此需要更大容量的硬盘。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200703123.png) ### 使用缓存改善性能 @@ -47,7 +50,7 @@ permalink: /pages/db2390/ - 本地缓存访问速度更快,但缓存数据量有限,同时存在与应用程序争用内存的情况。 - 分布式缓存可以采用集群方式,理论上可以做到不受内存容量限制的缓存服务。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200705172.png) ### 负载均衡 @@ -55,7 +58,7 @@ permalink: /pages/db2390/ - **特征**:**多台服务器通过负载均衡同时向外部提供服务,解决单一服务器处理能力和存储空间不足的问题。** - **描述**:使用集群是系统解决高并发、海量数据问题的常用手段。通过向集群中追加资源,提升系统的并发处理能力,使得服务器的负载压力不再成为整个系统的瓶颈。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200704005.png) ### 数据库读写分离 @@ -63,7 +66,7 @@ permalink: /pages/db2390/ - **特征**:目前大部分的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,可以将一台数据库服务器的数据更新同步到一台服务器上。**网站利用数据库的主从热备功能,实现数据库读写分离,从而改善数据库负载压力。** - **描述**:应用服务器在写操作的时候,访问主数据库,主数据库通过主从复制机制将数据更新同步到从数据库。这样当应用服务器在读操作的时候,访问从数据库获得数据。为了便于应用程序访问读写分离后的数据库,通常在应用服务器端使用专门的数据访问模块,使数据库读写分离的对应用透明。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200707199.png) ### 多级缓存 @@ -73,7 +76,7 @@ permalink: /pages/db2390/ - CDN 部署在网络提供商的机房,使用户在请求网站服务时,可以从距离自己最近的网络提供商机房获取数据; - 而反向代理则部署在网站的中心机房,当用户请求到达中心机房后,首先访问的服务器时反向代理服务器,如果反向代理服务器中缓存着用户请求的资源,就将其直接返回给用户。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200710745.png) ### 业务拆分 @@ -83,7 +86,7 @@ permalink: /pages/db2390/ - **纵向拆分**:**将一个大应用拆分为多个小应用**,如果新业务较为独立,那么就直接将其设计部署为一个独立的 Web 应用系统。纵向拆分相对较为简单,通过梳理业务,将较少相关的业务剥离即可。 - **横向拆分**:**将复用的业务拆分出来,独立部署为分布式服务**,新增业务只需要调用这些分布式服务横向拆分需要识别可复用的业务,设计服务接口,规范服务依赖关系。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200710835.png) ### 分库分表 @@ -91,7 +94,7 @@ permalink: /pages/db2390/ - **特征**:**数据库采用分布式数据库。** - **描述**:分布式数据库是数据库拆分的最后方法,只有在单表数据规模非常庞大的时候才使用。不到不得已时,更常用的数据库拆分手段是业务分库,将不同的业务数据库部署在不同的物理服务器上。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200711984.png) ### 分布式组件 @@ -99,7 +102,7 @@ permalink: /pages/db2390/ - **特征**:**系统引入 NoSQL 数据库及搜索引擎。** - **描述**:NoSQL 数据库及搜索引擎对可伸缩的分布式特性具有更好的支持。应用服务器通过统一数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200711267.png) ### 微服务 @@ -107,7 +110,7 @@ permalink: /pages/db2390/ - **特征**:**公共业务提取出来,独立部署。由这些可复用的业务连接数据库,通过分布式服务提供共用业务服务。** - 描述:大型网站的架构演化到这里,基本上大多数的技术问题都得以解决,诸如跨数据中心的实时数据同步和具体网站业务相关的问题也都可以组合改进现有技术架构来解决。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200711681.png) ## 架构设计的考量 @@ -333,7 +336,7 @@ permalink: /pages/db2390/ 四层的结构最常见。
- +
- 表现层(presentation):用户界面,负责视觉和用户互动 @@ -361,7 +364,7 @@ permalink: /pages/db2390/ 事件驱动架构(event-driven architecture)就是通过事件进行通信的软件架构。它分成四个部分。
- +
- 事件队列(event queue):接收事件的入口 @@ -372,7 +375,7 @@ permalink: /pages/db2390/ 对于简单的项目,事件队列、分发器和事件通道,可以合为一体,整个软件就分成事件代理和事件处理器两部分。
- +
优点 @@ -395,7 +398,7 @@ permalink: /pages/db2390/ 内核(core)通常只包含系统运行的最小功能。插件则是互相独立的,插件之间的通信,应该减少到最低,避免出现互相依赖的问题。
- +
优点 @@ -417,7 +420,7 @@ permalink: /pages/db2390/ 每一个服务就是一个独立的部署单元(separately deployed unit)。这些单元都是分布式的,互相解耦,通过远程通信协议(比如 REST、SOAP)联系。
- +
微服务架构分成三种实现模式。 @@ -446,7 +449,7 @@ permalink: /pages/db2390/ 它的高扩展性,主要原因是没使用中央数据库,而是把数据都复制到内存中,变成可复制的内存数据单元。然后,业务处理能力封装成一个个处理单元(prcessing unit)。访问量增加,就新建处理单元;访问量减少,就关闭处理单元。由于没有中央数据库,所以扩展性的最大瓶颈消失了。由于每个处理单元的数据都在内存里,最好要进行数据持久化。
- +
这个模式主要分成两部分:处理单元(processing unit)和虚拟中间件(virtualized middleware)。 @@ -476,4 +479,4 @@ permalink: /pages/db2390/ - [《大型网站技术架构:核心原理与案例分析》](https://item.jd.com/11322972.html) - 《从 0 开始学架构》 -- [软件架构入门- 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2016/09/software-architecture.html) \ No newline at end of file +- [软件架构入门- 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2016/09/software-architecture.html) diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/03.\347\263\273\347\273\237\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/03.\347\263\273\347\273\237\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" index dd98d588d1..0c3f7948de 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/03.\347\263\273\347\273\237\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/03.\347\263\273\347\273\237\351\253\230\346\200\247\350\203\275\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: 系统高性能架构 date: 2018-07-05 15:11:00 +order: 03 categories: - 设计 - 架构 @@ -153,7 +154,7 @@ permalink: /pages/a49605/ PPC 是 Process Per Connection 的缩写,其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求,这是传统的 UNIX 网络服务器所采用的模型。基本的流程图是: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200608103701.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200608103701.png) - 父进程接受连接(图中 accept)。 - 父进程“fork”子进程(图中 fork)。 @@ -172,7 +173,7 @@ PPC 模式中,当连接进来时才 fork 新进程来处理连接请求,由 顾名思义,prefork 就是提前创建进程(pre-fork)。系统在启动的时候就预先创建好进程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去 fork 进程的操作,让用户访问更快、体验更好。prefork 的基本示意图是: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200608104151.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200608104151.png) prefork 的实现关键就是多个子进程都 accept 同一个 socket,当有新的连接进入时,操作系统保证只有一个进程能最后 accept 成功。但这里也存在一个小小的问题:“惊群”现象,就是指虽然只有一个子进程能 accept 成功,但所有阻塞在 accept 上的子进程都会被唤醒,这样就导致了不必要的进程调度和上下文切换了。幸运的是,操作系统可以解决这个问题,例如 Linux 2.6 版本后内核已经解决了 accept 惊群问题。 @@ -184,7 +185,7 @@ TPC 是 Thread Per Connection 的缩写,其含义是指每次有新的连接 TPC 的基本流程是: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200608104311.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200608104311.png) - 父进程接受连接(图中 accept)。 - 父进程创建子线程(图中 pthread)。 @@ -212,7 +213,7 @@ TPC 模式中,当连接进来时才创建新的线程来处理连接请求, - 主进程 accept,然后将连接交给某个线程处理。 - 子线程都尝试去 accept,最终只有一个线程 accept 成功,方案的基本示意图如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200608104922.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200608104922.png) Apache 服务器的 MPM worker 模式本质上就是一种 prethread 方案,但稍微做了改进。Apache 服务器会首先创建多个进程,每个进程里面再创建多个线程,这样做主要是为了考虑稳定性,即:即使某个子进程里面的某个线程异常导致整个子进程退出,还会有其他子进程继续提供服务,不会导致整个服务器全部挂掉。 @@ -258,7 +259,7 @@ Reactor 模式的核心组成部分包括 Reactor 和处理资源池(进程池 **高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法**。 -> 缓存解决方案请参考:[负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) +> 缓存解决方案请参考:[负载均衡](https://dunwu.github.io/waterdrop/pages/98a1c1/) ### 代码优化 @@ -309,13 +310,13 @@ Reactor 模式的核心组成部分包括 Reactor 和处理资源池(进程池 **读写分离的基本原理是将数据库读写操作分散到不同的节点上** -> 详细解决方案参考:[读写分离](https://dunwu.github.io/blog/pages/3faf18/) +> 详细解决方案参考:[读写分离](https://dunwu.github.io/waterdrop/pages/3faf18/) #### 数据库分库分表 **数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果**。 -> 详细解决方案参考:[分库分表](https://dunwu.github.io/blog/pages/e1046e/) +> 详细解决方案参考:[分库分表](https://dunwu.github.io/waterdrop/pages/e1046e/) #### Nosql @@ -380,7 +381,7 @@ CDN 一般缓存的是静态资源。 CDN 的本质仍然是一个缓存,而且将数据缓存在离用户最近的地方,使用户已最快速度获取数据,即所谓网络访问第一跳。
- +
### 反向代理 @@ -388,7 +389,7 @@ CDN 的本质仍然是一个缓存,而且将数据缓存在离用户最近的 传统代理服务器位于浏览器一侧,代理浏览器将 HTTP 请求发送到互联网上,而反向代理服务器位于网站机房一侧,代理网站服务器接收 HTTP 请求。
- +
反向代理服务器可以配置缓存功能加速 Web 请求,当用户第一次访问静态内容时,静态内容就会被缓存在反向代理服务器上。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/04.\347\263\273\347\273\237\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/04.\347\263\273\347\273\237\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" index a9dfdf2563..f2e7f1186b 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/04.\347\263\273\347\273\237\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/04.\347\263\273\347\273\237\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: 系统高可用架构 date: 2018-07-05 15:11:00 +order: 04 categories: - 设计 - 架构 @@ -77,15 +78,15 @@ permalink: /pages/9a462f/ 学习高可用架构,首先需要了解分布式基础理论:CAP 和 BASE。 -然后,很多著名的分布式系统,都利用选举机制,来保证主节点宕机时的故障恢复。如果要深入理解选举机制,有必要了解:[Paxos 算法](https://dunwu.github.io/blog/pages/0276bb/) 和 [Raft 算法](https://dunwu.github.io/blog/pages/4907dc/)。Paxos 和 Raft 是为了实现分布式系统中高可用架构而提出的共识性算法,已经成为业界标准。 +然后,很多著名的分布式系统,都利用选举机制,来保证主节点宕机时的故障恢复。如果要深入理解选举机制,有必要了解:[Paxos 算法](https://dunwu.github.io/waterdrop/pages/0276bb/) 和 [Raft 算法](https://dunwu.github.io/waterdrop/pages/4907dc/)。Paxos 和 Raft 是为了实现分布式系统中高可用架构而提出的共识性算法,已经成为业界标准。 CAP 定理又称为 CAP 原则,指的是:**在一个分布式系统中, `一致性(C:Consistency)`、`可用性(A:Availability)` 和 `分区容忍性(P:Partition Tolerance)`,最多只能同时满足其中两项**。 BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State)`** 和 **`最终一致性(Eventually Consistent)`** 三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 -> CAP 和 BASE 理论的详细说明请参考:[分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) +> CAP 和 BASE 理论的详细说明请参考:[分布式一致性](https://dunwu.github.io/waterdrop/pages/dac0e2/) > -> Paxos 和 Raft 的详细说明请参考:[Paxos 算法](https://dunwu.github.io/blog/pages/0276bb/) 和 [Raft 算法](https://dunwu.github.io/blog/pages/4907dc/) +> Paxos 和 Raft 的详细说明请参考:[Paxos 算法](https://dunwu.github.io/waterdrop/pages/0276bb/) 和 [Raft 算法](https://dunwu.github.io/waterdrop/pages/4907dc/) ## 架构模式 @@ -99,7 +100,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State - 主机负责读&写,并定期复制数据给备机。 - 一旦主机宕机,可以通过人工手段,将其中一个备节点作为主节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200614173351.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200614173351.png) **优点** @@ -125,7 +126,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State - 从机只负责读。 - 一旦主机宕机,可以通过人工手段,将其中一个从节点作为主节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200614173527.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200614173527.png) **优点** @@ -150,7 +151,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State 分片后的节点可以视为一个独立的子集,针对子集,任然需要保证高可用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200614184921.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200614184921.png) ## 高可用的应用 @@ -171,7 +172,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State 无状态的应用实现高可用架构十分简单,由于服务器不保存请求状态,那么所有服务器完全对等,在任意节点执行同样的请求,结果总是一致的。这种情况下,最简单的高可用方案就是使用负载均衡。 -> 负载均衡原理可以参考:[负载均衡基本原理](https://dunwu.github.io/blog/pages/98a1c1/) +> 负载均衡原理可以参考:[负载均衡基本原理](https://dunwu.github.io/waterdrop/pages/98a1c1/) ### 分布式 Session @@ -185,7 +186,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State - 应用服务器间的 session 复制共享 - 基于缓存的 session 共享 ✅ -> 分布式会话原理可以参考:[分布式会话基本原理](https://dunwu.github.io/blog/pages/95e45f/) +> 分布式会话原理可以参考:[分布式会话基本原理](https://dunwu.github.io/waterdrop/pages/95e45f/) ## 高可用的服务 @@ -290,7 +291,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State #### 失效确认
- +
判断服务器宕机的手段有两种:**心跳检测**和**访问失败报告**。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/05.\347\263\273\347\273\237\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/05.\347\263\273\347\273\237\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" index 24d4c222cb..496e546c77 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/05.\347\263\273\347\273\237\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/05.\347\263\273\347\273\237\344\274\270\347\274\251\346\200\247\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: 系统伸缩性架构 date: 2018-07-05 15:11:00 +order: 05 categories: - 设计 - 架构 @@ -33,7 +34,7 @@ permalink: /pages/1e5251/ ### HTTP 重定向负载均衡
- +
利用 HTTP 重定向协议实现负载均衡。 @@ -47,7 +48,7 @@ permalink: /pages/1e5251/ 利用 DNS 处理域名解析请求的同时进行负载均衡处理的一种方案。
- +
在 DNS 服务器中配置多个 A 记录,如: @@ -75,7 +76,7 @@ DNS 域名解析负载均衡的缺点: 大多数反向代理服务器同时提供反向代理和负载均衡的功能。
- +
反向代理服务器的优点是部署简单。缺点是反向代理服务器是所有请求和响应的中转站,其性能可能会成为瓶颈。 @@ -85,7 +86,7 @@ DNS 域名解析负载均衡的缺点: 在网络层通过修改请求目标地址进行负载均衡。
- +
负载均衡服务器(网关服务器)在操作系统内核获取网络数据包,根据负载均衡算法计算得到一台真实 Web 服务器 10.0.0.1,然后将目的 IP 地址修改为 10.0.0.1,不需要通过用户进程。真实 Web 服务器处理完成后,响应数据包回到负载均衡服务器,负载均衡服务器再将数据包原地址修改为自身的 IP 地址(114.100.80.10)发送给浏览器。 @@ -97,7 +98,7 @@ IP 负载均衡在内核完成数据分发,所以处理性能优于反向代 数据链路层负载均衡是指在通信协议的数据链路层修改 mac 地址进行负载均衡。
- +
这种方式又称作三角传输方式,负载均衡数据分发过程中不修改 IP 地址,只修改目的 mac 地址,通过配置真实物理服务器集群所有机器虚拟 IP 和负载均衡服务器 IP 地址一致,从而达到不修改数据包的源地址和目的地址就可以进行数据分发的目的,由于实际处理请求的真实物理服务器 IP 和数据请求目的 IP 一致,不需要通过负载均衡服务器进行地址转换,可将响应数据包直接返回给用户浏览器,避免负载均衡服务器网卡带宽成为瓶颈。这种负载方式又称作直接路由方式。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/06.\347\263\273\347\273\237\346\211\251\345\261\225\346\200\247\346\236\266\346\236\204.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/06.\347\263\273\347\273\237\346\211\251\345\261\225\346\200\247\346\236\266\346\236\204.md" index a44810e65a..d4c24e08bc 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/06.\347\263\273\347\273\237\346\211\251\345\261\225\346\200\247\346\236\266\346\236\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/06.\347\263\273\347\273\237\346\211\251\345\261\225\346\200\247\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: 系统扩展性架构 date: 2018-07-05 15:11:00 +order: 06 categories: - 设计 - 架构 @@ -82,7 +83,7 @@ SOA 提出了 3 个关键概念。 ### 微内核 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220429183229.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220429183229.png) 微内核的核心系统设计的关键技术有:插件管理、插件连接和插件通信。 @@ -121,7 +122,7 @@ SOA 提出了 3 个关键概念。 消息生产者应用程序通过远程访问接口将消息推送给消息队列服务器,消息队列服务器将消息写入本地内存队列后立即返回成功响应给消息生产者。消息队列服务器根据消息订阅列表查找订阅该消息的消息消费者应用程序,将消息队列中的消息按照先进先出(FIFO)的原则将消息通过远程通信接口发送给消息消费者程序。
- +
在伸缩性方面,由于消息队列服务器上的数据可以看作是即时处理的,因此类似于无状态的服务器,伸缩性设计比较简单。将新服务器加入分布式消息队列集群中,通知生产者服务器更改消息队列服务器列表即可。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/07.\347\263\273\347\273\237\345\256\211\345\205\250\346\200\247\346\236\266\346\236\204.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/07.\347\263\273\347\273\237\345\256\211\345\205\250\346\200\247\346\236\266\346\236\204.md" index 0a24ba673b..0bd1de17ff 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/07.\347\263\273\347\273\237\345\256\211\345\205\250\346\200\247\346\236\266\346\236\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/07.\347\263\273\347\273\237\345\256\211\345\205\250\346\200\247\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: 系统安全性架构 date: 2018-07-05 15:11:00 +order: 07 categories: - 设计 - 架构 @@ -34,8 +35,6 @@ SSO 需要解决多个异构系统之间的问题: - 应用服务器间的 Session 复制共享 - 缺点:**占用过多内存**;**同步过程占用网络带宽以及服务器处理器时间**。 - 基于缓存的 Session 共享 ✅ (推荐方案) - 不过需要程序自身控制 Session 读写,可以考虑基于 spring-session + redis 这种成熟的方案来处理。 -> 详情请参考:[Session 原理](../theory/distributed-session.md) - #### Cookie 跨域 **Cookie 不能跨域**!比如:浏览器不会把 www.google.com 的 cookie 传给 www.baidu.com。 @@ -65,7 +64,7 @@ CAS 分为两部分,CAS Server 和 CAS Client CAS 工作流程: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119195646.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119195646.png) 1. 用户访问 CAS Client A(业务系统),第一次访问,重定向到认证服务中心(CAS Server)。CAS Server 发现当前请求中没有 Cookie,再重定向到 CAS Server 的登录页面。重定向请求的 URL 中包含访问地址,以便认证成功后直接跳转到访问页面。 2. 用户在登录页面输入用户名、密码等认证信息,认证成功后,CAS Server 生成 TGT,再用 TGT 生成一个 ST。然后返回 ST 和 TGC(Cookie)给浏览器。 @@ -74,7 +73,7 @@ CAS 工作流程: 5. 此时,如果登录另一个 CAS Client B,会先重定向到 CAS Server,CAS Server 可以判断这个 CAS Client B 是第一次访问,但是本地有 TGC,所以无需再次登录。用 TGC 创建一个 ST,返回给浏览器。 6. 重复类似 3、4 步骤。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119202448.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119202448.png) 以上了归纳总结如下: @@ -402,13 +401,13 @@ grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA 每个用户关联一个或多个角色,每个角色关联一个或多个权限,从而可以实现了非常灵活的权限管理。角色可以根据实际业务需求灵活创建,这样就省去了每新增一个用户就要关联一遍所有权限的麻烦。简单来说 RBAC 就是:用户关联角色,角色关联权限。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119210359.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119210359.png) ### 角色继承 角色继承(Hierarchical Role) 就是指角色可以继承于其他角色,在拥有其他角色权限的同时,自己还可以关联额外的权限。这种设计可以给角色分组和分层,一定程度简化了权限管理工作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119210528.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119210528.png) #### 职责分离(Separation of Duty) @@ -651,7 +650,7 @@ CSRF 能做的事太多: > > - [Wiki 词条 - 跨站请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) > - [浅谈 CSRF 攻击方式](http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html) -> - [「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) +> - [“每日一题”CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[“每日一题”CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) > - [WEB 安全之-CSRF(跨站请求伪造)](https://www.jianshu.com/p/855395f9603b) ### SQL 注入 @@ -770,7 +769,7 @@ MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统 应用场景:将用户密码以消息摘要形式保存到数据库中。 -> :point_right: 参考阅读: [Java 编码和加密](https://dunwu.github.io/blog/pages/a249ff/) +> :point_right: 参考阅读: [Java 编码和加密](https://dunwu.github.io/waterdrop/pages/a249ff/) ### 加密算法 @@ -790,7 +789,7 @@ MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统 应用场景:HTTPS 传输中浏览器使用的数字证书实质上是经过权威机构认证的非对称加密公钥。 -> :point_right: 参考阅读: [Java 编码和加密](https://dunwu.github.io/blog/pages/a249ff/) +> :point_right: 参考阅读: [Java 编码和加密](https://dunwu.github.io/waterdrop/pages/a249ff/) #### 密钥安全管理 @@ -897,7 +896,7 @@ SSL/TLS 协议的基本过程是这样的: - [Web 安全测试之 XSS](https://www.cnblogs.com/TankXiao/archive/2012/03/21/2337194.html) - [Wiki 词条 - 跨站请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) - [浅谈 CSRF 攻击方式](http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html) -- [「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) +- [“每日一题”CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[“每日一题”CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) - [WEB 安全之-CSRF(跨站请求伪造)](https://www.jianshu.com/p/855395f9603b) - [Wiki 词条 - SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A) - [避免 SQL 注入](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/09.4.md) @@ -909,4 +908,4 @@ SSL/TLS 协议的基本过程是这样的: - http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html - [CAS 实现 SSO 单点登录原理](http://www.coin163.com/java/cas/cas.html) - [权限系统设计模型分析(DAC,MAC,RBAC,ABAC)](https://www.jianshu.com/p/ce0944b4a903) -- [RBAC 模型:基于用户-角色-权限控制的一些思考](http://www.woshipm.com/pd/1150093.html) \ No newline at end of file +- [RBAC 模型:基于用户-角色-权限控制的一些思考](http://www.woshipm.com/pd/1150093.html) diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/08.\345\244\247\345\236\213\347\263\273\347\273\237\346\240\270\345\277\203\346\212\200\346\234\257.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/08.\345\244\247\345\236\213\347\263\273\347\273\237\346\240\270\345\277\203\346\212\200\346\234\257.md" index 7f722051c7..0e4acf8dd7 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/08.\345\244\247\345\236\213\347\263\273\347\273\237\346\240\270\345\277\203\346\212\200\346\234\257.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/08.\345\244\247\345\236\213\347\263\273\347\273\237\346\240\270\345\277\203\346\212\200\346\234\257.md" @@ -1,6 +1,7 @@ --- title: 大型系统核心技术 date: 2018-07-09 00:00:00 +order: 08 categories: - 设计 - 架构 @@ -23,8 +24,6 @@ permalink: /pages/8cbae8/ ## 分布式事务 -> 参考:[分布式原理#4-分布式事务问题](分布式原理.md#4-分布式事务问题) - ## 分布式锁 Java 原生 API 虽然有并发锁,但并没有提供分布式锁的能力,所以针对分布式场景中的锁需要解决的方案。 @@ -156,7 +155,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 缺点:当服务器节点宕机时,将丢失该服务器节点上的所有 Session。
- +
### Session Replication @@ -166,7 +165,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 缺点:占用过多内存;同步过程占用网络带宽以及服务器处理器时间。
- +
### Session Server @@ -176,7 +175,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 缺点:需要去实现存取 Session 的代码。
- +
## 分布式存储 @@ -186,8 +185,6 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 1. 数据分布:就是把数据分块存在不同的服务器上(分库分表)。 2. 数据复制:让所有的服务器都有相同的数据,提供相当的服务。 -> 参考:[分布式原理.md#2-数据分布](分布式原理.md#2-数据分布) - ## 分布式缓存 使用缓存的好处: @@ -207,8 +204,6 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 - 应用内缓存:如:EHCache - 分布式缓存:如:Memached、Redis -> 参考:[分布式原理.md#6-分布式缓存问题](分布式原理.md#6-分布式缓存问题) - ## 分布式计算 ## 负载均衡 @@ -220,13 +215,13 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 轮询算法把每个请求轮流发送到每个服务器上。下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1,(2, 4, 6) 的请求会被发送到服务器 2。
- +
该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2)。
- +
#### 加权轮询(Weighted Round Robbin) @@ -234,7 +229,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。例如下图中,服务器 1 被赋予的权值为 5,服务器 2 被赋予的权值为 1,那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1,(6) 请求会被发送到服务器 2。
- +
#### 最少连接(least Connections) @@ -242,13 +237,13 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。
- +
最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。
- +
#### 加权最少连接(Weighted Least Connection) @@ -256,7 +251,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。
- +
#### 随机算法(Random) @@ -264,7 +259,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 把请求随机发送到服务器上。和轮询算法类似,该算法比较适合服务器性能差不多的场景。
- +
#### 源地址哈希法 (IP Hash) @@ -275,7 +270,7 @@ ZooKeeper 版本的分布式锁问题相对比较来说少。 - 缺点:不利于集群扩展,后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。
- +
### 实现 @@ -290,7 +285,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 - 如果负载均衡器宕机,就无法访问该站点。
- +
#### DNS 重定向 @@ -302,7 +297,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 - DNS 查找表可能会被客户端缓存起来,那么之后的所有请求都会被重定向到同一个服务器。
- +
#### 修改 MAC 地址 @@ -310,7 +305,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 使用 LVS(Linux Virtual Server)这种链路层负载均衡器,根据负载情况修改请求的 MAC 地址。
- +
#### 修改 IP 地址 @@ -318,7 +313,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 在网络层修改请求的目的 IP 地址。
- +
#### 代理自动配置 @@ -331,7 +326,7 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 PAC 服务器是用来判断一个请求是否要经过代理。
- +
## 资料 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/09.\347\263\273\347\273\237\346\265\213\350\257\225\346\236\266\346\236\204.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/09.\347\263\273\347\273\237\346\265\213\350\257\225\346\236\266\346\236\204.md" index d6fe9df28b..a0ddefed06 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/09.\347\263\273\347\273\237\346\265\213\350\257\225\346\236\266\346\236\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/09.\347\263\273\347\273\237\346\265\213\350\257\225\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: 系统测试架构 date: 2019-12-10 17:00:00 +order: 09 categories: - 设计 - 架构 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/README.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/README.md" index 0f5a9c6eef..41b0bbff7b 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/00.\347\273\274\345\220\210/README.md" @@ -10,6 +10,7 @@ tags: - 架构 permalink: /pages/f3d238/ hidden: true +index: false --- # 架构综合 @@ -39,4 +40,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/01.\345\276\256\346\234\215\345\212\241\347\256\200\344\273\213.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/01.\345\276\256\346\234\215\345\212\241\347\256\200\344\273\213.md" index 21b54f5090..64e9316636 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/01.\345\276\256\346\234\215\345\212\241\347\256\200\344\273\213.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/01.\345\276\256\346\234\215\345\212\241\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: 微服务简介 date: 2022-04-15 16:42:51 +order: 01 categories: - 设计 - 架构 @@ -45,7 +46,7 @@ permalink: /pages/012075/ - **团队协作成本高**:单体应用的代码往往在一个工程中,而一个工程中的开发人员越多,显然沟通成本越高。 - **可用性差**:因为所有的功能开发最后都部署到同一个 WAR 包里,运行在同一个 Tomcat 进程之中,一旦某一功能涉及的代码或者资源有问题,那就会影响整个 WAR 包中部署的功能。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627091754.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627091754.png) 服务化:本地方法调用 转为 远程方法调用(RPC) @@ -191,7 +192,7 @@ permalink: /pages/012075/ ### 注册中心 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220415171843.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220415171843.png) 有了服务的接口描述,下一步要解决的问题就是服务的发布和订阅,就是说你提供了一个服务,如何让外部想调用你的服务的人知道。这个时候就需要一个类似注册中心的角色,服务提供者将自己提供的服务以及地址登记到注册中心,服务消费者则从注册中心查询所需要调用的服务的地址,然后发起请求。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/02.\345\276\256\346\234\215\345\212\241\344\271\213\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/02.\345\276\256\346\234\215\345\212\241\344\271\213\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" index 3a1123efb1..7e2ec5ebbf 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/02.\345\276\256\346\234\215\345\212\241\344\271\213\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/02.\345\276\256\346\234\215\345\212\241\344\271\213\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" @@ -1,6 +1,7 @@ --- title: 微服务之注册和发现 date: 2023-05-15 19:08:50 +order: 02 categories: - 设计 - 架构 @@ -16,79 +17,326 @@ permalink: /pages/44b4c3/ # 微服务之注册和发现 -## 微服务的服务定义 +## 服务注册和发现的基本原理 -想要构建微服务,首先要解决的问题是,服务提供者如何发布一个服务,服务消费者如何引用这个服务。具体来说,就是这个服务的接口名是什么?调用这个服务需要传递哪些参数?接口的返回值是什么类型?以及一些其他接口描述信息。 +服务定义是服务提供者和服务消费者之间的约定,但是在微服务架构中,如何达成这个约定呢?这就依赖于服务注册和发现机制。 -最常见的服务定义的方式有以下几种: +### 注册和发现的角色 -- **REST API** - - REST API 方式主要被用作 HTTP 或者 HTTPS 协议的接口定义,即使在非微服务架构体系下,也被广泛采用。由于 HTTP 本身就是公开标准网络协议,所以几乎没有什么额外学习成本。 - - 代表 RPC 框架:Spring Cloud Eureka -- **XML 配置** - - **XML 配置方式通过在服务提供者和服务消费者之间维持一份对等的 XML 配置文件,来保证服务消费者按照服务提供者的约定来进行服务调用**。在这种方式下,如果服务提供者变更了接口定义,不仅需要更新服务提供者加载的接口描述文件 server.xml,还需要同时更新服务消费者加载的接口描述文件 client.xml。但这种方式对业务代码侵入性比较高,XML 配置有变更的时候,服务消费者和服务提供者都要更新,所以适合公司内部联系比较紧密的业务之间采用。 - - 代表 RPC 框架:阿里的 [Dubbo](https://github.com/apache/dubbo)(XML 配置示例:[基于 Spring XML 开发微服务应用](https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/spring-xml/))、微博的 Motan -- **IDL 文件** - - IDL 就是接口描述语言(interface description language)的缩写,**IDL 主要用于跨语言的服务之间的调用**。 - - 代表 RPC 框架:阿里的 [Dubbo](https://github.com/apache/dubbo)(XML 配置示例:[IDL 定义跨语言服务](https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/idl/)),Facebook 的 [Thrift](https://github.com/apache/thrift),Google 的 [gRPC](https://github.com/grpc/grpc) -- **SDK 配置** - - 提供特定编程语言的发布、订阅 API,以此来约定接口定义。 - - 代表 RPC 框架:支持 Java 的 Dubbo(API 方式示例:[基于 Dubbo API 开发微服务应用](https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/api/)、Java 注解方式示例:[基于 Spring Boot Starter 开发微服务应用](https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/spring-boot/)) +在微服务架构下,服务注册和发现机制中主要有三种角色: -具体采用哪种服务定义方式是根据实际情况决定的: +- **服务提供者**(RPC Server / Provider) +- **服务消费者**(RPC Client / Consumer) +- **服务注册中心**(Registry) -- 如果只是企业内部之间的服务调用,并且都是 Java 语言的话,选择 SDK 配置、XML 配置方式是最简单的; -- 如果企业内部存在多个跨语言服务,建议使用 IDL 文件方式进行描述服务; -- 如果还存在对外开放服务调用的情形的话,使用 REST API 方式则更加通用。 +服务发现通常依赖于**注册中心**来协调服务发现的过程,其步骤如下: -## 服务注册和发现的工作原理 +1. 服务提供者将接口信息以注册到注册中心。 +2. 服务消费者从注册中心读取和订阅服务提供者的地址信息。 +3. 如果有可用的服务,注册中心会主动通知服务消费者。 +4. 服务消费者根据可用服务的地址列表,调用服务提供者的接口。 -服务定义是服务提供者和服务消费者之间的约定,但是在微服务架构中,如何达成这个约定呢?这就依赖于服务注册和发现机制。 +这个过程很像是生活中的房屋租赁,房东将租房信息挂到中介公司,房客从中介公司查找租房信息。房客如果想要租房东的房子,通过中介公司牵线搭桥,联系上房东,双方谈妥签订协议,就可以正式建立起租赁关系。 -在微服务架构下,服务注册和发现机制中主要有三种角色: +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220415171843.png) -- **服务提供者**(RPC Server / Provider) -- **服务消费者**(RPC Client / Consumer) -- **服务注册中心**(Registry) +主流的服务注册与发现的解决方案,主要有两种: + +- **应用内注册与发现**:注册中心提供服务端和客户端的 SDK,业务应用通过引入注册中心提供的 SDK,通过 SDK 与注册中心交互,来实现服务的注册和发现。 +- **应用外注册与发现**:业务应用本身不需要通过 SDK 与注册中心打交道,而是通过其他方式与注册中心交互,间接完成服务注册与发现。 + +### 应用内注册与发现 + +**应用内注册与发现**方案是:注册中心提供服务端和客户端的 SDK,业务应用通过引入注册中心提供的 SDK,通过 SDK 与注册中心交互,来实现服务的注册和发现。最典型的案例要属 Netflix 开源的 Eureka,官方架构图如下: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220418204148.jfif) + +Eureka 的架构主要由三个重要的组件组成: + +- **Eureka Server**:注册中心的服务端,实现了服务信息注册、存储以及查询等功能。 +- **服务端的 Eureka Client**:集成在服务端的注册中心 SDK,服务提供者通过调用 SDK,实现服务注册、反注册等功能。 +- **客户端的 Eureka Client**:集成在客户端的注册中心 SDK,服务消费者通过调用 SDK,实现服务订阅、服务更新等功能。 + +### 应用外注册与发现 + +**应用外注册与发现**方案是:业务应用本身不需要通过 SDK 与注册中心打交道,而是通过其他方式与注册中心交互,间接完成服务注册与发现。最典型的案例是开源注册中心 Consul。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220418204352.png) + +Consul 实现应用外服务注册和发现主要依靠三个重要的组件: + +- Consul:注册中心的服务端,实现服务注册信息的存储,并提供注册和发现服务。 +- [Registrator](https://github.com/gliderlabs/registrator):一个开源的第三方服务管理器项目,它通过监听服务部署的 Docker 实例是否存活,来负责服务提供者的注册和销毁。 +- [Consul Template](https://github.com/hashicorp/consul-template):定时从注册中心服务端获取最新的服务提供者节点列表并刷新 LB 配置(比如 Nginx 的 upstream),这样服务消费者就通过访问 Nginx 就可以获取最新的服务提供者信息。 + +## 注册中心的基本功能 + +从服务注册和发现的流程,可以看出,**注册中心是服务发现的核心组件**。常见的注册中心组件有:Nacos、Consul、Zookeeper 等。 + +注册中心的实现主要涉及几个问题:注册中心需要提供哪些接口,该如何部署;如何存储服务信息;如何监控服务提供者节点的存活;如果服务提供者节点有变化如何通知服务消费者,以及如何控制注册中心的访问权限。 + +### 元数据定义 + +构建微服务的首要问题是:服务提供者和服务消费者通信时,如何达成共识。具体来说,就是这个服务的接口名是什么?调用这个服务需要传递哪些参数?接口的返回值是什么类型?以及一些其他接口描述信息。 + +常见的定义服务元数据的方式有: + +- **XML 文件** - 如果只是企业内部之间的服务调用,并且都是 Java 语言的话,选择 XML 配置方式是最简单的。 +- **IDL 文件** - 如果企业内部存在多个跨语言服务,建议使用 IDL 文件方式进行描述服务。 +- **REST API** - 如果存在对外开放服务调用的情形的话,使用 REST API 方式则更加通用。 + +#### XML 文件 + +**XML 配置方式通过在服务提供者和服务消费者之间维持一份对等的 XML 配置文件,来保证服务消费者按照服务提供者的约定来进行服务调用**。在这种方式下,如果服务提供者变更了接口定义,不仅需要更新服务提供者加载的接口描述文件 server.xml,还需要同时更新服务消费者加载的接口描述文件 client.xml。但这种方式对业务代码侵入性比较高,XML 配置有变更的时候,服务消费者和服务提供者都要更新,所以适合公司内部联系比较紧密的业务之间采用。支持 XML 文件的主流 RPC 有:阿里的 [Dubbo](https://github.com/apache/dubbo)(XML 配置示例:[基于 Spring XML 开发微服务应用](https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/spring-xml/))、微博的 Motan。 + +XML 文件这种方式的服务发布和引用主要分三个步骤: + +(1)服务提供者定义接口,并实现接口。 -一般来讲,注册中心的工作流程是: +```java +// The demo service definition. +service DemoService { + rpc sayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} +``` + +(2)服务提供者进程启动时,通过加载 xml 配置文件将接口暴露出去。 + +```xml + + + + + + + +``` + +(3)服务消费者进程启动时,通过加载 xml 配置文件来引入要调用的接口。 + +```xml + + + + + +``` + +#### IDL 文件 + +IDL 就是接口描述语言(interface description language)的缩写,通过一种中立、通用的方式来描述接口,使得在不同的平台上运行的对象和不同语言编写的程序可以相互通信交流。也就是说,**IDL 主要用于跨语言的服务之间的调用**。支持 IDL 文件的主流 RPC 有:阿里的 [Dubbo](https://github.com/apache/dubbo)(XML 配置示例:[IDL 定义跨语言服务](https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/idl/)),Facebook 的 [Thrift](https://github.com/apache/thrift),Google 的 [gRPC](https://github.com/grpc/grpc) 。 + +以 gRPC 协议为例,gRPC 协议使用 Protobuf 简称 proto 文件来定义接口名、调用参数以及返回值类型。比如文件 helloword.proto 定义了一个接口 SayHello 方法,它的请求参数是 HelloRequest,它的返回值是 HelloReply。 -- 服务提供者在启动时,根据服务发布文件中配置的发布信息向注册中心注册自己的服务。 -- 服务消费者在启动时,根据消费者配置文件中配置的服务信息向注册中心订阅自己所需要的服务。 -- 注册中心返回服务提供者地址列表给服务消费者。 -- 当服务提供者发生变化,比如有节点新增或者销毁,注册中心将变更通知给服务消费者。 +```java +// The greeter service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + rpc SayHelloAgain (HelloRequest) returns (HelloReply) {} -## 注册中心的核心功能 +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} +``` + +假如服务提供者使用的是 Java 语言,那么利用 protoc 插件即可自动生成 Server 端的 Java 代码。 + +```java +private class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + + @Override + public void sayHelloAgain(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello again " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } +} +``` + +假如服务消费者使用的也是 Java 语言,那么利用 protoc 插件即可自动生成 Client 端的 Java 代码。 + +```java +public void greet(String name) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + try { + response = blockingStub.sayHelloAgain(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); +} +``` + +假如服务消费者使用的是其他语言,也可以利用相应的插件生成代码。 + +由此可见,gRPC 协议的服务描述是通过 proto 文件来定义接口的,然后再使用 protoc 来生成不同语言平台的客户端和服务端代码,从而具备跨语言服务调用能力。 + +有一点特别需要注意的是,在描述接口定义时,IDL 文件需要对接口返回值进行详细定义。如果接口返回值的字段比较多,并且经常变化时,采用 IDL 文件方式的接口定义就不太合适了。一方面可能会造成 IDL 文件过大难以维护,另一方面只要 IDL 文件中定义的接口返回值有变更,都需要同步所有的服务消费者都更新,管理成本就太高了。 + +#### REST API + +REST API 方式主要被用作 HTTP 或者 HTTPS 协议的接口定义,即使在非微服务架构体系下,也被广泛采用。由于 HTTP 本身就是公开标准网络协议,所以几乎没有什么额外学习成本。支持 REST API 的主流 RPC 有:Eureka,下面以 Eureka 为例。 + +服务提供者定义接口 + +```java +@RestController +public class ProviderController { + + private final DiscoveryClient discoveryClient; + + public ProviderController(DiscoveryClient discoveryClient) { + this.discoveryClient = discoveryClient; + } + + @GetMapping("/send") + public String send() { + String services = "Services: " + discoveryClient.getServices(); + System.out.println(services); + return services; + } + +} +``` + +服务消费者消费接口 + +```java +@RestController +public class ConsumerController { + + private final LoadBalancerClient loadBalancerClient; + private final RestTemplate restTemplate; + + public ConsumerController(LoadBalancerClient loadBalancerClient, + RestTemplate restTemplate) { + this.loadBalancerClient = loadBalancerClient; + this.restTemplate = restTemplate; + } + + @GetMapping("/recv") + public String recv() { + ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-provider"); + String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/send"; + System.out.println(url); + return restTemplate.getForObject(url, String.class); + } + +} +``` + +### 元数据存储 注册中心本质上是一个用于保存元数据的分布式存储。你如果明白了这一点,就会了解实现一个注册中心的所有要点都是围绕这个目标去构建的。 -- **元数据存储** - - **关键信息** - 注册中心主要用于存储服务信息,而服务信息一般包含三部分内容: - - **服务名** - - **分组** - 服务一般会分成多个不同的分组,每个分组的目的不同。一般来说有下面几种分组方式: - - 核心与非核心 - 从业务的核心程度来分。 - - 机房 - 从机房的维度来分。 - - 不同环境 - 从业务场景维度来区分。 - - **节点信息** - 节点信息又包括节点地址和其他节点信息。 - - **层次化的结构** - 注册中心存储服务信息一般采用层次化的结构。以 ZooKeeper 为例: - - 在 ZooKeeper 中,数据按目录层级存储,每个目录叫作 znode,并且其有一个唯一的路径标识。 - - znode 可以包含数据和子 znode。 - - znode 中的数据可以有多个版本,比如某一个 znode 下存有多个数据版本,那么查询这个路径下的数据需带上版本信息。 -- **注册中心 API** - 既然是分布式存储,势必要提供支持读写数据的接口,也就是 API,一般来说,需要支持以下功能: - - **服务注册** - 服务提供者通过调用服务注册接口来完成服务注册。 - - **服务反注册** - 服务提供者通过调用服务反注册接口来完成服务注销。 - - **心跳汇报** - 服务提供者通过调用心跳汇报接口完成节点存活状态上报。 - - **服务订阅** - 服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。 - - **服务变更查询** - 服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。 - - **服务查询** - 查询注册中心当前注册了哪些服务信息。 - - **服务修改** - 修改注册中心中某一服务的信息。 -- **服务健康检查** - 注册中心需要一种机制,来判断注册的服务是否还“活着”,即使用长连接或心跳探测方式来检查服务健康状态——这也是分布式应用常见的做法。以 ZooKeeper 注册中心为例: - - ZooKeeper 客户端和服务端维持的是一个长连接。连接成功后,会生成一个全局唯一的 Session ID,客户端定期发送心跳消息,服务端收到后重置会话超时时间。如果超时,则认为连接结束。 - - 如果一个服务将 ZooKeeper 作为服务注册中心,一旦连接超时,ZooKeeper 会认为这个服务节点已经不可用,就会将其信息删除。 -- **服务状态订阅** - 当服务状态变更,如服务提供者某节点 IP 宕机,需要让服务消费者感知变化,可以基于订阅者模式实现:服务消费者可以监听服务提供者的节点信息,一旦服务提供者的节点信息变化,就可以获取到变更状态。 -- **集群部署** - 注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。根据 [CAP 理论](https://en.wikipedia.org/wiki/CAP_theorem),三种特性无法同时达成,必须在可用性和一致性之间做取舍。于是,根据不同侧重点,注册中心可以分为 CP 和 AP 两个阵营: - - **CP 型注册中心** - **牺牲可用性来换取数据强一致性**,最典型的例子就是 ZooKeeper,etcd,Consul 了。ZooKeeper 集群内只有一个 Leader,而且在 Leader 无法使用的时候通过 Paxos 算法选举出一个新的 Leader。这个 Leader 的目的就是保证写信息的时候只向这个 Leader 写入,Leader 会同步信息到 Followers,这个过程就可以保证数据的强一致性。但如果多个 ZooKeeper 之间网络出现问题,造成出现多个 Leader,发生脑裂的话,注册中心就不可用了。而 etcd 和 Consul 集群内都是通过 Raft 协议来保证强一致性,如果出现脑裂的话, 注册中心也不可用。 - - **AP 型注册中心** - **牺牲一致性(只保证最终一致性)来换取可用性**,最典型的例子就是 Eureka 了。对比下 Zookeeper,Eureka 不用选举一个 Leader,每个 Eureka 服务器单独保存服务注册地址,因此有可能出现数据信息不一致的情况。但是当网络出现问题的时候,每台服务器都可以完成独立的服务。 +想要构建微服务,首先要解决的问题是,服务提供者如何发布一个服务,服务消费者如何引用这个服务。具体来说,就是这个服务的接口名是什么?调用这个服务需要传递哪些参数?接口的返回值是什么类型?以及一些其他接口描述信息。 + +服务的**元数据信息**通常有以下信息: + +- 服务节点信息,如 IP、端口等。 +- 接口定义,如接口名、请求参数、响应参数等。 +- 请求失败的重试次数 +- 序列化方式 +- 压缩方式 +- 通信协议 +- 等等 + +在具体存储时,注册中心一般会按照“服务 - 分组 - 节点信息”的**层次化的结构**来存储。以 ZooKeeper 为例: + +- 在 ZooKeeper 中,数据按目录层级存储,每个目录叫作 znode,并且其有一个唯一的路径标识。 +- znode 可以包含数据和子 znode。 +- znode 中的数据可以有多个版本,比如某一个 znode 下存有多个数据版本,那么查询这个路径下的数据需带上版本信息。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_1.png) + +### 注册中心 API + +既然是分布式存储,势必要提供支持读写数据的接口,也就是 API,一般来说,需要支持以下功能: + +- **服务注册接口**:服务提供者通过调用服务注册接口来完成服务注册。 +- **服务反注册接口**:服务提供者通过调用服务反注册接口来完成服务注销。 +- **心跳汇报接口**:服务提供者通过调用心跳汇报接口完成节点存活状态上报。 +- **服务订阅接口**:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。 +- **服务变更查询接口**:服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。 + +除此之外,为了便于管理,注册中心还必须提供一些后台管理的 API,例如: + +- **服务查询接口**:查询注册中心当前注册了哪些服务信息。 +- **服务修改接口**:修改注册中心中某一服务的信息。 + +### 服务健康检测 + +注册中心除了要支持最基本的服务注册和服务订阅功能以外,还必须具备对服务提供者节点的健康状态检测功能,这样才能保证注册中心里保存的服务节点都是可用的。**注册中心通常使用长连接或心跳探测方式检查服务健康状态**。 + +还是以 ZooKeeper 为例,它是基于 ZooKeeper 客户端和服务端的长连接和会话超时控制机制,来实现服务健康状态检测的。在 ZooKeeper 中,客户端和服务端建立连接后,会话也随之建立,并生成一个全局唯一的 Session ID。服务端和客户端维持的是一个长连接,在 SESSION_TIMEOUT 周期内,服务端会检测与客户端的链路是否正常,具体方式是通过客户端定时向服务端发送心跳消息(ping 消息),服务器重置下次 SESSION_TIMEOUT 时间。如果超过 SESSION_TIMEOUT 后服务端都没有收到客户端的心跳消息,则服务端认为这个 Session 就已经结束了,ZooKeeper 就会认为这个服务节点已经不可用,将会从注册中心中删除其信息。 + +### 服务状态变更通知 + +一旦注册中心探测到有服务提供者节点新加入或者被剔除,就必须立刻通知所有订阅该服务的服务消费者,刷新本地缓存的服务节点信息,确保服务调用不会请求不可用的服务提供者节点。注册中心通常基于服务状态订阅来实现服务状态变更通知。 + +继续以 ZooKeeper 为例,基于 ZooKeeper 的 Watcher 机制,来实现服务状态变更通知给服务消费者的。服务消费者在调用 ZooKeeper 的 getData 方法订阅服务时,还可以通过监听器 Watcher 的 process 方法获取服务的变更,然后调用 getData 方法来获取变更后的数据,刷新本地缓存的服务节点信息。 + +### 集群部署 + +注册中心作为服务提供者和服务消费者之间沟通的桥梁,它的重要性不言而喻。所以注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。根据 [CAP 理论](https://en.wikipedia.org/wiki/CAP_theorem),三种特性无法同时达成,必须在可用性和一致性之间做取舍。于是,根据不同侧重点,注册中心可以分为 CP 和 AP 两个阵营: + +- **CP 型注册中心** - **牺牲可用性来换取数据强一致性**,最典型的例子就是 ZooKeeper,etcd,Consul 了。ZooKeeper 集群内只有一个 Leader,而且在 Leader 无法使用的时候通过 Paxos 算法选举出一个新的 Leader。这个 Leader 的目的就是保证写信息的时候只向这个 Leader 写入,Leader 会同步信息到 Followers,这个过程就可以保证数据的强一致性。但如果多个 ZooKeeper 之间网络出现问题,造成出现多个 Leader,发生脑裂的话,注册中心就不可用了。而 etcd 和 Consul 集群内都是通过 Raft 协议来保证强一致性,如果出现脑裂的话, 注册中心也不可用。 +- **AP 型注册中心** - **牺牲一致性(只保证最终一致性)来换取可用性**,最典型的例子就是 Eureka 了。对比下 Zookeeper,Eureka 不用选举一个 Leader,每个 Eureka 服务器单独保存服务注册地址,因此有可能出现数据信息不一致的情况。但是当网络出现问题的时候,每台服务器都可以完成独立的服务。 + +以开源注册中心 ZooKeeper 为例,ZooKeeper 集群中包含多个节点,服务提供者和服务消费者可以同任意一个节点通信,因为它们的数据一定是相同的,这是为什么呢?这就要从 ZooKeeper 的工作原理说起: + +- 每个 Server 在内存中存储了一份数据,Client 的读请求可以请求任意一个 Server。 +- ZooKeeper 启动时,将从实例中选举一个 leader(Paxos 协议)。 +- Leader 负责处理数据更新等操作(ZAB 协议)。 +- 一个更新操作成功,当且仅当大多数 Server 在内存中成功修改 。 + +通过上面这种方式,ZooKeeper 保证了高可用性以及数据一致性。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) ## 注册中心的扩展功能 @@ -134,33 +382,15 @@ permalink: /pages/44b4c3/ 这个阈值比例可以根据实际业务的冗余度来确定,我通常会把这个比例设定在 20%,就是说注册中心不能摘除超过 20% 的节点。因为大部分情况下,节点的变化不会这么频繁,只有在网络抖动或者业务明确要下线大批量节点的情况下才有可能发生。而业务明确要下线大批量节点的情况是可以预知的,这种情况下可以关闭阈值保护;而正常情况下,应该打开阈值保护,以防止网络抖动时,大批量可用的服务节点被摘除。 -### 黑/白名单机制 - -通常注册中心会有多套环境,区分开发、测试、线上等环境。如果弄错了,会出现意想不到的后果,为此需要引入黑、白名单保护机制。只有添加到注册中心白名单内的 RPC Server,才能够调用注册中心的注册接口,这样的话可以避免测试环境中的节点意外跑到线上环境中去。 - -## 注册中心的实现模式 +### 白名单机制 -### 应用内注册和发现 +在实际的微服务测试和部署时,通常包含多套环境,比如生产环境一套、测试环境一套。开发在进行业务自测、测试在进行回归测试时,一般都是用测试环境,部署的 RPC Server 节点注册到测试的注册中心集群。但经常会出现开发或者测试在部署时,错误的把测试环境下的服务节点注册到了线上注册中心集群,这样的话线上流量就会调用到测试环境下的 RPC Server 节点,可能会造成意想不到的后果。 -采用应用内注册与发现的方式,最典型的案例要属 Netflix 开源的 Eureka,官方架构图如下。 +为了防止这种情况发生,注册中心需要提供一个保护机制,你可以把注册中心想象成一个带有门禁的房间,只有拥有门禁卡的 RPC Server 才能进入。在实际应用中,注册中心可以提供一个白名单机制,只有添加到注册中心白名单内的 RPC Server,才能够调用注册中心的注册接口,这样的话可以避免测试环境中的节点意外跑到线上环境中去。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200720194412.png) +### 静态注册中心 -对着这张图,我来介绍下 Eureka 的架构,它主要由三个重要的组件组成: - -- Eureka Server:注册中心的服务端,实现了服务信息注册、存储以及查询等功能。 -- 服务端的 Eureka Client:集成在服务端的注册中心 SDK,服务提供者通过调用 SDK,实现服务注册、反注册等功能。 -- 客户端的 Eureka Client:集成在客户端的注册中心 SDK,服务消费者通过调用 SDK,实现服务订阅、服务更新等功能。 - -### 应用外注册和发现 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200720194519.png) - -通过这张架构图,可以看出来使用 Consul 实现应用外服务注册和发现主要依靠三个重要的组件: - -- Consul:注册中心的服务端,实现服务注册信息的存储,并提供注册和发现服务。 -- [Registrator](https://github.com/gliderlabs/registrator):一个开源的第三方服务管理器项目,它通过监听服务部署的 Docker 实例是否存活,来负责服务提供者的注册和销毁。 -- [Consul Template](https://github.com/hashicorp/consul-template):定时从注册中心服务端获取最新的服务提供者节点列表并刷新 LB 配置(比如 Nginx 的 upstream),这样服务消费者就通过访问 Nginx 就可以获取最新的服务提供者信息。 +因为服务提供者是向服务消费者提供服务的,服务是否可用,服务消费者应该比注册中心更清楚。因此,可以直接在服务消费者端,根据调用服务提供者是否成功来判定服务提供者是否可用。如果服务消费者调用某一个服务提供者节点连续失败超过一定次数,可以在本地内存中将这个节点标记为不可用。并且每隔一段固定时间,服务消费者都要向标记为不可用的节点发起保活探测,如果探测成功了,就将标记为不可用的节点再恢复为可用状态,重新发起调用。 ## 参考资料 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/03.\345\276\256\346\234\215\345\212\241\344\271\213\346\234\215\345\212\241\350\260\203\347\224\250.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/03.\345\276\256\346\234\215\345\212\241\344\271\213\346\234\215\345\212\241\350\260\203\347\224\250.md" index 23a7a17beb..1f7dd53d4b 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/03.\345\276\256\346\234\215\345\212\241\344\271\213\346\234\215\345\212\241\350\260\203\347\224\250.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/03.\345\276\256\346\234\215\345\212\241\344\271\213\346\234\215\345\212\241\350\260\203\347\224\250.md" @@ -1,6 +1,7 @@ --- title: 微服务之服务调用 date: 2023-05-15 19:08:50 +order: 03 categories: - 设计 - 架构 @@ -16,81 +17,211 @@ permalink: /pages/c1c7b2/ # 微服务之服务调用 +## RPC 简介 + 通过注册中心,服务消费者和服务提供者就可以感知彼此。但是,要实现交互还必须解决通信问题。 -在单体应用中,一次服务调用发生在同一台机器上的同一个进程内部,因此也被称为本地方法调用。在微服务应用中,由于服务提供者和服务消费者运行在不同物理机器上的不同进程内,因此也被称为远程方法调用,简称 RPC(Remote Procedure Call)。 +在单体应用中,一次服务调用发生在同一台机器上的同一个进程内部,因此也被称为本地方法调用。在微服务应用中,由于服务提供者和服务消费者运行在不同物理机器上的不同进程内,因此也被称为**远程方法调用**,简称 **RPC(Remote Procedure Call)**。 + +RPC 是微服务架构的基石,它提供了一种应用间通信的方式。RPC 的主要作用是: + +- **屏蔽远程调用跟本地调用的差异**,让用户像调用本地一样去调用远程方法。 +- **隐藏底层网络通信的复杂性**,让用户更聚焦于业务逻辑。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619101023.png) + +## RPC 核心原理 RPC 是如何像本地方法调用一样,完成一次请求处理的呢?我们不妨推导一二。首先,服务消费者和服务提供者通常位于网络上两个不同地址,要想交换信息,必须先建立网络连接;建立网络连接后,如果要想识别彼此的信息,必须遵循相同的通信协议;服务提供者和服务消费者,需要采用某种方式数据传输;为了减少传输数据量,还要对数据进行压缩,即序列化。 -通过以上分析,我们由此可以得知 RPC 调用需要解决四个问题: +它的通信流程中需要注意以下环节: + +- **传输方式**:RPC 是一个远程调用,因此必然需要通过网络传输数据,且 RPC 常用于业务系统之间的数据交互,需要保证其可靠性,所以 RPC 一般默认采用 TCP 来传输。 +- **序列化**:在网络中传输的数据只能是二进制数据,而 RPC 请求时,发送的都是对象。因此,请求方需要将请求参数转为二进制数据,即**序列化**。RPC 响应方接受到请求,要将二进制数据转换为请求参数,需要**反序列化**。 +- **通信协议**:请求方和响应方要互相识别彼此的信息,需要约定好彼此数据的格式,即协议。大多数的协议至少分成两部分,分别是数据头和消息体。数据头一般用于身份识别,包括协议标识、数据大小、请求类型、序列化类型等信息;消息体主要是请求的业务参数信息和扩展属性等。 +- **动态代理**:为了屏蔽底层通信细节,使用户聚焦自身业务,因此 RPC 框架一般引入了动态代理,通过依赖注入等技术,拦截方法调用,完成远程调用的通信逻辑。 + +下图诠释了以上环节是如何串联起来的: + + + +## 通信协议 + +### 通信协议的作用 + +只有二进制才能在网络中传输,所以 RPC 请求在发送到网络中之前,需要把方法调用的请求参数转成二进制;转成二进制后,写入本地 Socket 中,然后被网卡发送到网络设备中。 + +在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(合并的前提是同一个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP 窗口大小。对于服务提供方应用来说,他会从 TCP 通道里面收到很多的二进制数据,那这时候怎么识别出哪些二进制是第一个请求的呢? + +这就好比让你读一篇没有标点符号的文章,你要怎么识别出每一句话到哪里结束呢?很简单啊,我们加上标点,完成断句就好了。为了避免语义不一致的事情发生,我们就需要在发送请求的时候设定一个边界,然后在收到请求的时候按照这个设定的边界进行数据分割。这个边界语义的表达,就是我们所说的协议。 + +**通信协议**要解决的是:客户端和服务端如何建立连接、管理连接以及服务端如何处理请求的问题。 + +### 常见网络协议 + +HTTP 通信是基于应用层 HTTP 协议的,而 HTTP 协议又是基于传输层 TCP 协议的。一次 HTTP 通信过程就是发起一次 HTTP 调用,而一次 HTTP 调用就会建立一个 TCP 连接,经历**三次握手**的过程来建立连接。完成请求后,再经历一次**四次挥手**的过程来断开连接。 + +TCP 通信的过程分为四个步骤:**服务器监听**、**客户端请求**、**连接确认**、**数据传输**。当客户端和服务端建立网络连接后,就可以发起请求了。但网络不一定总是可靠的,经常会遇到网络闪断、连接超时、服务端宕机等各种异常,通常的处理手段有两种:**链路存活检测**和**断连重试**。 + +通过两种通信方式的对比,不难看出:HTTP 通信由于每次都要建立 TCP 连接,而建立连接又较为耗时,所以 **HTTP 通信性能是不如 TCP 通信的**。 + +### 为何需要设计 RPC 协议 + +既然有了现成的 HTTP 协议,还有必要设计 RPC 协议吗? + +有必要。因为 HTTP 这些通信标准协议,数据包中的实际请求数据相对于数据包本身要小很多,有很多无用的内容;并且 HTTP 属于无状态协议,无法将请求和响应关联,每次请求要重新建立连接。这对于高性能的 RPC 来说,HTTP 协议难以满足需求,所以有必要设计一个**紧凑的私有协议**。 + +### 如何设计 RPC 协议 + +首先,必须先明确消息的边界,即确定消息的长度。因此,至少要分为:消息长度+消息内容两部分。 + +接下来,我们会发现,在使用过程中,仅消息长度,不足以明确通信中的很多细节:如序列化方式是怎样的?是否消息压缩?压缩格式是怎样的?如果协议发生变化,需要明确协议版本等等。 + +综上,一个 RPC 协议大概会由下图中的这些参数组成: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619102052.png) + +### 可扩展的协议 + +前面所述的协议属于定长协议头,那也就是说往后就不能再往协议头里加新参数了,如果加参 +数就会导致线上兼容问题。 + +为了保证能平滑地升级改造前后的协议,我们有必要设计一种支持可扩展的协议。其关键在于让协议头支持可扩展,扩展后协议头的长度就不能定长了。那要实现读取不定长的协议头里面的内容,在这之前肯定需要一个固定的地方读取长度,所以我们需要一个固定的写入协议头的长度。整体协议就变成了三部分内容:固定部分、协议头内容、协议体内容。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619102833.png) + +## 序列化 + +> 有兴趣深入了解 JDK 序列化方式,可以参考:[Java 序列化](https://dunwu.github.io/waterdrop/pages/2b2f0f/) + +由于,网络传输的数据必须是二进制数据,而调用方请求的出参、入参都是对象。因此,必须将对象转换可传输的二进制,并且要求转换算法是可逆的。 -- 如何进行网络传输? -- 数据传输采用什么协议? -- 数据该如何序列化和反序列化? +- **序列化(serialize)**:序列化是将对象转换为二进制数据。 +- **反序列化(deserialize)**:反序列化是将二进制数据转换为对象。 -## 网络传输方式 +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619110947.png) -网络传输方式即服务提供者和服务消费者之间的数据传输采用哪种方式。是同步还是异步?是在单连接上传输,还是多路复用。 +序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。 -常见的网络传输方式有三种: +### 序列化技术 -**同步阻塞方式(BIO)** - 客户端每发一次请求,服务端就生成一个线程去处理。当客户端同时发起的请求很多时,服务端需要创建很多的线程去处理每一个请求,如果达到了系统最大的线程数瓶颈,新来的请求就没法处理了。 +Java 领域,常见的序列化技术如下 + +- JDK 序列化:JDK 内置的二进制序列化方式 +- 其他二进制序列化 + - [Thrift](https://github.com/apache/thrift) + - [Protobuf](https://github.com/protocolbuffers/protobuf) + - [Hessian](http://hessian.caucho.com/doc/hessian-overview.xtp) +- JSON 序列化 + - [Jackson](https://github.com/FasterXML/jackson) + - [Gson](https://github.com/google/gson) + - [Fastjson](https://github.com/alibaba/fastjson) + +### 序列化技术选型 + +市面上有如此多的序列化技术,那么我们在应用时如何选择呢? + +序列化技术选型,需要考量的维度,根据重要性从高到低,依次有: + +- **安全性**:是否存在漏洞。如果存在漏洞,就有被攻击的可能性。 +- **兼容性**:版本升级后的兼容性是否很好,是否支持更多的对象类型,是否是跨平台、跨语言的。服务调用的稳定性与可靠性,要比服务的性能更加重要。 +- **性能** + - **时间开销**:序列化、反序列化的耗时性能自然越小越好。 + - **空间开销**:序列化后的数据越小越好,这样网络传输效率就高。 +- **易用性**:类库是否轻量化,API 是否简单易懂。 + +鉴于以上的考量,序列化技术的选型建议如下: + +- JDK 序列化:性能较差,且有很多使用限制,不建议使用。 +- [Thrift](https://github.com/apache/thrift)、[Protobuf](https://github.com/protocolbuffers/protobuf):适用于**对性能敏感,对开发体验要求不高**。 +- [Hessian](http://hessian.caucho.com/doc/hessian-overview.xtp):适用于**对开发体验敏感,性能有要求**。 +- [Jackson](https://github.com/FasterXML/jackson)、[Gson](https://github.com/google/gson)、[Fastjson](https://github.com/alibaba/fastjson):适用于对序列化后的数据要求有**良好的可读性**(转为 json 、xml 形式)。 + +### 序列化问题 + +由于 RPC 每次通信,都要经过序列化、反序列化的过程,所以序列化方式,会直接影响 RPC 通信的性能。除了选择合适的序列化技术,如何合理使用序列化也非常重要。 + +RPC 序列化常见的使用不当的情况如下: + +- **对象过于复杂、庞大** - 对象过于复杂、庞大,会降低序列化、反序列化的效率,并增加传输开销,从而导致响应时延增大。 + + - 过于复杂:存在多层的嵌套,比如 A 对象关联 B 对象,B 对象又聚合 C 对象,C 对象又关联聚合很多其他对象 + - 过于庞大:比如一个大 List 或者大 Map + +- **对象有复杂的继承关系** - 对象关系越复杂,就越浪费性能,同时又很容易出现序列化上的问题。大多数序列化框架在进行序列化时,如果发现类有继承关系,会不停地寻找父类,遍历属性。 +- **使用序列化框架不支持的类作为入参类** - 比如 Hessian 框架,他天然是不支持 LinkHashMap、LinkedHashSet 等,而且大多数情况下最好不要使用第三方集合类,如 Guava 中的集合类,很多开源的序列化框架都是优先支持编程语言原生的对象。因此如果入参是集合类,应尽量选用原生的、最为常用的集合类,如 HashMap、ArrayList。 + +### 序列化要点 + +前面已经列举了常见的序列化问题,既然明确了问题,就要针对性预防。RPC 序列化时要注意以下几点: + +1. 对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚; +2. 入参对象与返回值对象体积不要太大,更不要传太大的集合; +3. 尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类; +4. 对象不要有复杂的继承关系,最好不要有父子类的情况。 + +## 网络传输 + +一次 RPC 调用,本质就是服务消费者与服务提供者间的一次网络信息交换的过程。可见,通信时 RPC 实现的核心。 + +常见的网络 IO 模型有:同步阻塞(BIO)、同步非阻塞(NIO)、异步非阻塞(AIO)。 + +### 同步阻塞方式(BIO) + +同步阻塞方式的工作流程大致为:客户端每发一次请求,服务端就生成一个线程去处理。当客户端同时发起的请求很多时,服务端需要创建很多的线程去处理每一个请求,如果达到了系统最大的线程数瓶颈,新来的请求就没法处理了。 BIO 适用于连接数比较小的业务场景,这样的话不至于系统中没有可用线程去处理请求。这种方式写的程序也比较简单直观,易于理解。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630212345.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630212345.png) -**同步非阻塞方式 (NIO)** - 客户端每发一次请求,服务端并不是每次都创建一个新线程来处理,而是通过 I/O 多路复用技术进行处理。就是把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。这种方式的优势是开销小,不用为每个请求创建一个线程,可以节省系统开销。 +### 同步非阻塞方式 (NIO) -NIO 适用于连接数比较多并且请求消耗比较轻的业务场景,比如聊天服务器。这种方式相比 BIO,相对来说编程比较复杂。 +**同步非阻塞方式 (NIO)** 的工作流程大致为:客户端每发一次请求,服务端并不是每次都创建一个新线程来处理,而是通过 I/O 多路复用技术进行处理。就是把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。这种方式的优势是开销小,不用为每个请求创建一个线程,可以节省系统开销。 -BIO 与 NIO 最重要的区别是数据打包和传输的方式:**BIO 以流的方式处理数据,而 NIO 以块的方式处理数据**。 +#### IO 多路复用 -- **面向流的 BIO 一次处理一个字节数据**:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。 -- **面向块的 NIO 一次处理一个数据块**,按块处理数据比按流处理数据要快得多。但是面向块的 NIO 缺少一些面向流的 BIO 所具有的优雅性和简单性。 +IO 多路复用(Reactor 模式)在高并发场景下使用最为广泛,很多知名软件都应用了这一技术,如:Netty、Redis、Nginx 等。什么是 IO 多路复用?字面上的理解,多路就是指多个通道,也就是多个网络连接的 IO,而复用就是指多个通道复用在一个复用器上。IO 多路复用分为 select,poll 和 epoll。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630212248.png) +#### 零拷贝 -**异步非阻塞方式 (AIO)** - 客户端只需要发起一个 I/O 操作然后立即返回,等 I/O 操作真正完成以后,客户端会得到 I/O 操作完成的通知,此时客户端只需要对数据进行处理就好了,不需要进行实际的 I/O 读写操作,因为真正的 I/O 读取或者写入操作已经由内核完成了。这种方式的优势是客户端无需等待,不存在阻塞等待问题。 +系统内核处理 IO 操作分为两个阶段——等待数据和拷贝数据。等待数据,就是系统内核在等待网卡接收到数据后,把数据写到内核中;而拷贝数据,就是系统内核在获取到数据后,将数据拷贝到用户进程的空间中。 -AIO 适用于连接数比较多而且请求消耗比较重的业务场景,比如涉及 I/O 操作的相册服务器。这种方式相比另外两种,编程难度最大,程序也不易于理解。 +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717154300) -> 在实际的 Java 网络通信开发中,可以使用一些成熟的网络通信框架,如:Netty 等。 +应用进程的每一次写操作,都会把数据写到用户空间的缓冲区中,再由 CPU 将数据拷贝到系统内核的缓冲区中,之后再由 DMA 将这份数据拷贝到网卡中,最后由网卡发送出去。这里我们可以看到,一次写操作数据要拷贝两次才能通过网卡发送出去,而用户进程的读操作则是将整个流程反过来,数据同样会拷贝两次才能让应用程序读取到数据。 -## 通信协议 +应用进程的一次完整的读写操作,都需要在用户空间与内核空间中来回拷贝,并且每一次拷贝,都需要 CPU 进行一次上下文切换(由用户进程切换到系统内核,或由系统内核切换到用户进程),这样很浪费 CPU 和性能。 -**通信协议**要解决的是:服务提供者和服务消费者之间以什么样的**协议**进行网络通信。说白了,是要解决客户端和服务端如何建立连接、管理连接以及服务端如何处理请求的问题。是采用四层 TCP、UDP 协议,还是采用七层 HTTP 协议,还是采用其他协议?例如:Dubbo 基于 TCP 通信;而 Spring Cloud 基于 HTTP REST 通信。**TCP 通信方式,传输效率更高**;但是,HTTP 通信方式,天然支持对外服务。 +所谓的零拷贝,就是取消用户空间与内核空间之间的数据拷贝操作,应用进程每一次的读写操作,可以通过一种方式,直接将数据写入内核或从内核中读取数据,再通过 DMA 将内核中的数据拷贝到网卡,或将网卡中的数据 copy 到内核。 -HTTP 通信是基于应用层 HTTP 协议的,而 HTTP 协议又是基于传输层 TCP 协议的。一次 HTTP 通信过程就是发起一次 HTTP 调用,而一次 HTTP 调用就会建立一个 TCP 连接,经历**三次握手**的过程来建立连接。完成请求后,再经历一次**四次挥手**的过程来断开连接。 +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717154716.jfif) -TCP 通信的过程分为四个步骤:**服务器监听**、**客户端请求**、**连接确认**、**数据传输**。当客户端和服务端建立网络连接后,就可以发起请求了。但网络不一定总是可靠的,经常会遇到网络闪断、连接超时、服务端宕机等各种异常,通常的处理手段有两种:**链路存活检测**和**断连重试**。 +Netty 的零拷贝偏向于用户空间中对数据操作的优化,这对处理 TCP 传输中的拆包粘包问题有着重要的意义,对应用程序处理请求数据与返回数据也有重要的意义。 -通过两种通信方式的对比,不难看出:HTTP 通信由于每次都要建立 TCP 连接,而建立连接又较为耗时,所以 **HTTP 通信性能是不如 TCP 通信的**。 +Netty 框架中很多内部的 ChannelHandler 实现类,都是通过 CompositeByteBuf、slice、wrap 操作来处理 TCP 传输中的拆包与粘包问题的。 -除了 HTTP 协议和 TCP 协议以外,一些 RPC 框架还扩展支持了一些私有协议,如 Dubbo 支持的 Dubbo 协议,这里不一一列举。 +Netty 的 ByteBuffer 可以采用 Direct Buffers,使用堆外直接内存进行 Socketd 的读写 +操作,最终的效果与我刚才讲解的虚拟内存所实现的效果是一样的。 -## 序列化方式 +Netty 还提供 FileRegion 中包装 NIO 的 FileChannel.transferTo() 方法实现了零拷 +贝,这与 Linux 中的 sendfile 方式在原理上也是一样的。 -在微服务架构中,**序列化和反序列化**主要解决客户端和服务端采用哪种数据编/解码的问题。常见的序列化方式包括:XML、JSON;二进制类如:[thrift](https://github.com/apache/thrift)、[protobuf](https://github.com/protocolbuffers/protobuf)、[hessian](http://hessian.caucho.com/doc/hessian-overview.xtp)、JDK。 +#### NIO vs BIO -具体采用哪种序列化方式,主要取决于三个方面的因素: +NIO 适用于连接数比较多并且请求消耗比较轻的业务场景,比如聊天服务器。这种方式相比 BIO,相对来说编程比较复杂。 -- **支持数据结构类型的丰富度** - 数据结构种类支持的自然是越多越好,这样使用者的开发体验比较友好。 -- **跨语言支持** - 序列化方式是否支持跨语言也是一个很重要的因素,否则就无法应对异构服务场景。 -- **性能** - 主要看两点,一个是序列化后的压缩比,一个是序列化的速度。以常用的 PB 序列化和 JSON 序列化协议为例来对比分析,PB 序列化的压缩比和速度都要比 JSON 序列化高很多,所以对性能和存储空间要求比较高的系统选用 PB 序列化更合适;而 JSON 序列化虽然性能要差一些,但可读性更好,更适合对外部提供服务。 +BIO 与 NIO 最重要的区别是数据打包和传输的方式:**BIO 以流的方式处理数据,而 NIO 以块的方式处理数据**。 -> 更多序列化的内容可以参考:[Java 序列化](https://dunwu.github.io/blog/pages/2b2f0f/) +- **面向流的 BIO 一次处理一个字节数据**:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。 +- **面向块的 NIO 一次处理一个数据块**,按块处理数据比按流处理数据要快得多。但是面向块的 NIO 缺少一些面向流的 BIO 所具有的优雅性和简单性。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630212248.png) -微服务框架对比: +### 异步非阻塞方式 (AIO) -| | RPC | REST | -| -------- | ------------------------------------------------- | -------------------------------- | -| 耦合性 | 强耦合 | 松散耦合 | -| 协议 | Tcp | Http、Http2 | -| 序列化 | 二进制(Thrift、Protobuf、Hessian、Avro、JDK 等) | Xml、Json | -| 性能 | 高 | 低 | -| 客户端 | 对编程语言有限制 | 跨语言支持更好(支持 Http 即可) | -| 代表技术 | Dubbo、Motan、Tars、gRpc、Thrift | Spring Cloud | +**异步非阻塞方式 (AIO)** 的大致工作流程为:客户端只需要发起一个 I/O 操作然后立即返回,等 I/O 操作真正完成以后,客户端会得到 I/O 操作完成的通知,此时客户端只需要对数据进行处理就好了,不需要进行实际的 I/O 读写操作,因为真正的 I/O 读取或者写入操作已经由内核完成了。这种方式的优势是客户端无需等待,不存在阻塞等待问题。 + +AIO 适用于连接数比较多而且请求消耗比较重的业务场景,比如涉及 I/O 操作的相册服务器。这种方式相比另外两种,编程难度最大,程序也不易于理解。 ## 参考资料 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/10.\345\276\256\346\234\215\345\212\241\345\237\272\346\234\254\345\216\237\347\220\206.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/10.\345\276\256\346\234\215\345\212\241\345\237\272\346\234\254\345\216\237\347\220\206.md" index 9b2e0f9a23..881571a41b 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/10.\345\276\256\346\234\215\345\212\241\345\237\272\346\234\254\345\216\237\347\220\206.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/10.\345\276\256\346\234\215\345\212\241\345\237\272\346\234\254\345\216\237\347\220\206.md" @@ -1,6 +1,7 @@ --- title: 微服务基本原理 date: 2020-07-21 15:35:00 +order: 10 categories: - 设计 - 架构 @@ -17,7 +18,7 @@ permalink: /pages/aa7497/ ## 微服务技术架构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716195006.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716195006.png) **第一层:接入层** @@ -31,7 +32,7 @@ permalink: /pages/aa7497/ 比较细粒度的微服务层,提供基础的核心服务,公共服务。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716195117.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716195117.png) ## 服务通信 @@ -49,7 +50,7 @@ permalink: /pages/aa7497/ - 跨语言支持 - 性能 -> 👉 参考:[Java 序列化](https://dunwu.github.io/blog/pages/2b2f0f/) +> 👉 参考:[Java 序列化](https://dunwu.github.io/waterdrop/pages/2b2f0f/) ### 通信协议 @@ -118,7 +119,7 @@ permalink: /pages/aa7497/ ### 监控技术 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716204432.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716204432.png) - ELK 的技术栈比较成熟,应用范围也比较广,除了可用作监控系统外,还可以用作日志查询和分析。 - Graphite 是基于时间序列数据库存储的监控系统,并且提供了功能强大的各种聚合函数比如 sum、average、top5 等可用于监控分析,而且对外提供了 API 也可以接入其他图形化监控系统如 Grafana。 @@ -131,7 +132,7 @@ permalink: /pages/aa7497/ 微服务治理平台关键之处就在于它能够封装对微服务架构内的各个基础设施组件的调用,从而对外提供统一的服务操作 API,而且还提供了可视化的界面,以方便开发人员和运维人员操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716203729.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716203729.png) 服务治理的常用手段有: @@ -159,13 +160,13 @@ API 网关方式的核心要点是,所有的客户端和消费端都通过统 ### Zuul -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716201640.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716201640.png) 在 zuul 中, 整个请求的过程是这样的,首先将请求给 zuulservlet 处理,zuulservlet 中有一个 zuulRunner 对象,该对象中初始化了 RequestContext:作为存储整个请求的一些数据,并被所有的 zuulfilter 共享。zuulRunner 中还有 FilterProcessor,FilterProcessor 作为执行所有的 zuulfilter 的管理器。FilterProcessor 从 filterloader 中获取 zuulfilter,而 zuulfilter 是被 filterFileManager 所加载,并支持 groovy 热加载,采用了轮询的方式热加载。有了这些 filter 之后,zuulservelet 首先执行的 Pre 类型的过滤器,再执行 route 类型的过滤器,最后执行的是 post 类型的过滤器,如果在执行这些过滤器有错误的时候则会执行 error 类型的过滤器。执行完这些过滤器,最终将请求的结果返回给客户端。 ## 负载均衡 -> 参考:[负载均衡基本原理](https://dunwu.github.io/blog/pages/98a1c1/) +> 参考:[负载均衡基本原理](https://dunwu.github.io/waterdrop/pages/98a1c1/) ## 服务路由 @@ -294,7 +295,7 @@ function route(invokers){ 基础服务之间的调用:结合服务注册中心以及专属的具有负载均衡功能的客户端,如 Eureka+(restTemplate+Ribbon)或者 Eureka+Feign 聚合服务调用:结合服务注册中心以及专属的具有负载均衡功能的客户端,如 Eureka+(restTemplate+Ribbon)或者 Eureka+Feign -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716202409.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716202409.png) ### 外部服务调用 @@ -325,7 +326,7 @@ zuul 的核心是 ZuulServlet,一个请求核心流程:HttpServletRequest Spring Cloud 中使用的配置中心组件,只支持 Java 语言,配置存储在 git 中,变更配置也需要通过 git 操作,如果配置中心有配置变更,需要手动刷新。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716202911.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716202911.png) ## 链路追踪 @@ -344,7 +345,7 @@ Spring Cloud 中使用的配置中心组件,只支持 Java 语言,配置存 - **spanId**,用于标识一次 RPC 调用在分布式请求中的位置。当用户的请求进入系统后,处在 RPC 调用网络的第一层 A 时 spanId 初始值是 0,进入下一层 RPC 调用 B 的时候 spanId 是 0.1,继续进入下一层 RPC 调用 C 时 spanId 是 0.1.1,而与 B 处在同一层的 RPC 调用 E 的 spanId 是 0.2,这样的话通过 spanId 就可以定位某一次 RPC 请求在系统调用中所处的位置,以及它的上下游依赖分别是谁。 - **annotation**,用于业务自定义埋点数据,可以是业务感兴趣的想上传到后端的数据,比如一次请求的用户 UID。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716204658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716204658.png) ### 链路追踪的实现 @@ -383,7 +384,7 @@ Spring Cloud 中使用的配置中心组件,只支持 Java 语言,配置存 ### 链路追踪方案对比 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716205052.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716205052.png) ## 限流熔断 @@ -449,7 +450,7 @@ Mesos、Marathon、Kubernetes ## Service Mesh -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200721154106.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200721154106.png) ### Service Mesh 的实现原理 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/README.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/README.md" index 790deed24b..45a1509214 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/01.\345\276\256\346\234\215\345\212\241/README.md" @@ -12,6 +12,7 @@ tags: - 分布式 permalink: /pages/559360/ hidden: true +index: false --- # 微服务 @@ -44,4 +45,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/01.\347\273\274\350\277\260.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/01.\347\273\274\350\277\260.md" index 715b17dfdf..78be3bc08d 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/01.\347\273\274\350\277\260.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/01.\347\273\274\350\277\260.md" @@ -1,6 +1,7 @@ --- title: 权限认证综述 date: 2021-11-08 08:15:33 +order: 01 categories: - 设计 - 架构 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/02.\350\256\244\350\257\201.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/02.\350\256\244\350\257\201.md" index 03024af3c1..f430c4daf8 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/02.\350\256\244\350\257\201.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/02.\350\256\244\350\257\201.md" @@ -1,6 +1,7 @@ --- title: 认证设计 date: 2022-11-15 18:04:17 +order: 02 categories: - 设计 - 架构 @@ -46,8 +47,8 @@ Cookie 实际上是存储在客户端上的文本信息,并保留了各种跟 Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。 -- [Cookie 和 Session](https://dunwu.github.io/blog/pages/c46bff/) -- [分布式会话](https://dunwu.github.io/blog/pages/95e45f/) +- [Cookie 和 Session](https://dunwu.github.io/waterdrop/pages/c46bff/) +- [分布式会话](https://dunwu.github.io/waterdrop/pages/95e45f/) ## 单点登录 @@ -66,8 +67,6 @@ SSO 需要解决多个异构系统之间的问题: - 应用服务器间的 Session 复制共享 - 缺点:**占用过多内存**;**同步过程占用网络带宽以及服务器处理器时间**。 - 基于缓存的 Session 共享 ✅ (推荐方案) - 不过需要程序自身控制 Session 读写,可以考虑基于 spring-session + redis 这种成熟的方案来处理。 -> 详情请参考:[Session 原理](../theory/distributed-session.md) - ### Cookie 跨域 **Cookie 不能跨域**!比如:浏览器不会把 www.google.com 的 cookie 传给 www.baidu.com。 @@ -97,7 +96,7 @@ CAS 分为两部分,CAS Server 和 CAS Client CAS 工作流程: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119195646.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119195646.png) 1. 用户访问 CAS Client A(业务系统),第一次访问,重定向到认证服务中心(CAS Server)。CAS Server 发现当前请求中没有 Cookie,再重定向到 CAS Server 的登录页面。重定向请求的 URL 中包含访问地址,以便认证成功后直接跳转到访问页面。 2. 用户在登录页面输入用户名、密码等认证信息,认证成功后,CAS Server 生成 TGT,再用 TGT 生成一个 ST。然后返回 ST 和 TGC(Cookie)给浏览器。 @@ -106,7 +105,7 @@ CAS 工作流程: 5. 此时,如果登录另一个 CAS Client B,会先重定向到 CAS Server,CAS Server 可以判断这个 CAS Client B 是第一次访问,但是本地有 TGC,所以无需再次登录。用 TGC 创建一个 ST,返回给浏览器。 6. 重复类似 3、4 步骤。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119202448.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119202448.png) 以上了归纳总结如下: diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/03.\346\216\210\346\235\203.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/03.\346\216\210\346\235\203.md" index a0db39f17a..a634e8f5a7 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/03.\346\216\210\346\235\203.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/03.\346\216\210\346\235\203.md" @@ -1,6 +1,7 @@ --- title: 授权设计 date: 2022-11-15 17:48:06 +order: 03 categories: - 设计 - 架构 @@ -31,11 +32,11 @@ permalink: /pages/05473f/ 每个用户关联一个或多个角色,每个角色关联一个或多个权限,从而可以实现了非常灵活的权限管理。角色可以根据实际业务需求灵活创建,这样就省去了每新增一个用户就要关联一遍所有权限的麻烦。简单来说 RBAC 就是:用户关联角色,角色关联权限。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119210359.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119210359.png) 角色继承(Hierarchical Role) 就是指角色可以继承于其他角色,在拥有其他角色权限的同时,自己还可以关联额外的权限。这种设计可以给角色分组和分层,一定程度简化了权限管理工作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119210528.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119210528.png) ### 职责分离(Separation of Duty) diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/05.\345\256\211\345\205\250\346\274\217\346\264\236.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/05.\345\256\211\345\205\250\346\274\217\346\264\236.md" index 5ec6b19dad..eb1a57775a 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/05.\345\256\211\345\205\250\346\274\217\346\264\236.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/05.\345\256\211\345\205\250\346\274\217\346\264\236.md" @@ -1,6 +1,7 @@ --- title: 安全漏洞防护 date: 2022-11-16 15:51:30 +order: 05 categories: - 设计 - 架构 @@ -88,7 +89,7 @@ CSRF 能做的事太多: > > - [Wiki 词条 - 跨站请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) > - [浅谈 CSRF 攻击方式](http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html) -> - [「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[「每日一题」CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) +> - [“每日一题”CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378)[“每日一题”CSRF 是什么?](https://zhuanlan.zhihu.com/p/22521378) > - [WEB 安全之-CSRF(跨站请求伪造)](https://www.jianshu.com/p/855395f9603b) ## SQL 注入 @@ -187,4 +188,4 @@ MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统 > :point_right: 参考阅读: > -> - [拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A) \ No newline at end of file +> - [拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A) diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/06.\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/06.\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" index 60b14bbe32..34708eee2a 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/06.\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/06.\347\274\226\347\240\201\345\222\214\345\212\240\345\257\206.md" @@ -1,6 +1,7 @@ --- title: 编码和加密 date: 2021-05-24 15:41:47 +order: 06 categories: - 设计 - 架构 @@ -108,7 +109,7 @@ Base64 编码可用于在 HTTP 环境下传递较长的标识信息。在其他 签名时要使用私钥和待签名数据,验证时则需要公钥、签名值和待签名数据,其核心算法主要是消息摘要算法。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/java-message-digest-process.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/java-message-digest-process.jpg) 数字签名常用算法:**RSA**、**DSA**、**ECDSA** @@ -137,11 +138,11 @@ Base64 编码可用于在 HTTP 环境下传递较长的标识信息。在其他 一种是把明文信息划分为不同的组(或块)结构,分别对每个组(或块)进行加密、解密,称为分组密码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/symmetric-encryption.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/symmetric-encryption.png) 假设甲乙方作为通信双方。假定甲乙双方在消息传递前已商定加密算法,欲完成一次消息传递需要经过如下步骤。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/symmetric-encryption-progress.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/symmetric-encryption-progress.png) ### 对称加密工作模式 @@ -211,7 +212,7 @@ PBE 没有密钥概念,密钥在其他对称加密算法中是经过计算得 流程: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/password-based-encryption-progress.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/password-based-encryption-progress.png) ## 非对称加密 @@ -224,7 +225,7 @@ PBE 没有密钥概念,密钥在其他对称加密算法中是经过计算得 - 优点:非对称加密算法解决了对称加密算法的密钥分配问题,并极大地提高了算法安全性。 - 缺点:算法比对称算法更复杂,因此加密、解密速度都比对称算法慢很多。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/advanced/asymmetric-encryption.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/advanced/asymmetric-encryption.png) 非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/README.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/README.md" index 7f7ee17995..f9fbb6898a 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/02.\345\256\211\345\205\250/README.md" @@ -11,6 +11,7 @@ tags: - 安全 permalink: /pages/056621/ hidden: true +index: false --- # 安全架构 @@ -38,4 +39,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/README.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/README.md" index 4d2bf935c7..cdf364e636 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/README.md" @@ -10,6 +10,7 @@ tags: - 解决方案 permalink: /pages/c38eff/ hidden: true +index: false --- # 解决方案 @@ -49,4 +50,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\344\275\216\344\273\243\347\240\201\345\271\263\345\217\260.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\344\275\216\344\273\243\347\240\201\345\271\263\345\217\260.md" index 4de4a9ba41..b2ae309e5b 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\344\275\216\344\273\243\347\240\201\345\271\263\345\217\260.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\344\275\216\344\273\243\347\240\201\345\271\263\345\217\260.md" @@ -34,7 +34,7 @@ permalink: /pages/f90553/ Outsystems 就是采用该方案的典型产品,如下为产品截图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506193447.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506193447.png) #### 基于模型驱动的应用平台 @@ -44,7 +44,7 @@ Outsystems 就是采用该方案的典型产品,如下为产品截图: 明道云、伙伴云等都是此类方案的典型产品,如下为明道云的产品截图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506193656.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506193656.png) ### 核心要素 @@ -93,11 +93,11 @@ Mendix 帮助企业改善创新方式。通过使用可视化模型,在 Mendix 最后,由于生成的代码是应用于 Java Web 框架。生成的后端代码是 java 代码;前端代码是基于 vue + element-ui 生态的前端代码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506200045.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506200045.png) 代码生成规则对应的数据建模: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506200704.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506200704.png) 自动生成前后端代码。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\224\265\345\225\206.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\224\265\345\225\206.md" index d736eea101..363dfdbb8a 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\224\265\345\225\206.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\224\265\345\225\206.md" @@ -15,7 +15,7 @@ permalink: /pages/4ae6a4/ ## 基本业务架构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210805222544.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210805222544.jpg) ### 订单 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\237\255\345\234\260\345\235\200\346\234\215\345\212\241.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\237\255\345\234\260\345\235\200\346\234\215\345\212\241.md" index 17b28bccae..c0af454d85 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\237\255\345\234\260\345\235\200\346\234\215\345\212\241.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\237\255\345\234\260\345\235\200\346\234\215\345\212\241.md" @@ -248,7 +248,7 @@ class HitCounts(MRJob): **重要提示: 不要简单的从最初的设计直接跳到最终的设计** -说明您将迭代地执行这样的操作:1)**Benchmark/Load 测试**,2)**Profile** 出瓶颈,3)在评估替代方案和权衡时解决瓶颈,4)重复前面,可以参考[在 AWS 上设计一个可以支持百万用户的系统](../scaling_aws/README.md)这个用来解决如何迭代地扩展初始设计的例子。 +说明您将迭代地执行这样的操作:1)**Benchmark/Load 测试**,2)**Profile** 出瓶颈,3)在评估替代方案和权衡时解决瓶颈,4)重复前面,可以参考在 AWS 上设计一个可以支持百万用户的系统这个用来解决如何迭代地扩展初始设计的例子。 重要的是讨论在初始设计中可能遇到的瓶颈,以及如何解决每个瓶颈。比如,在多个 **Web 服务器** 上添加 **负载平衡器** 可以解决哪些问题? **CDN** 解决哪些问题?**Master-Slave Replicas** 解决哪些问题? 替代方案是什么和怎么对每一个替代方案进行权衡比较? diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" index 754130f8dc..0dfda59fb5 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/99.\350\247\243\345\206\263\346\226\271\346\241\210/\347\247\222\346\235\200\347\263\273\347\273\237\350\256\276\350\256\241.md" @@ -35,7 +35,7 @@ permalink: /pages/a963f0/ - **准(一致性)**:商品减库存方式非常关键,不能出现超卖。 - **快(高性能)**:整个请求链路,从前端到后端,依赖组件都要做到协同优化。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200720073346.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200720073346.png) ## 前端优化 @@ -84,7 +84,7 @@ permalink: /pages/a963f0/ - 答题(摇一摇):可以限制秒杀器并延缓请求。 - 分层过滤:采用漏斗式的设计尽可能拦截无效请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200720094300.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200720094300.png) ### 减库存 diff --git "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/README.md" "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/README.md" index ed746a5195..16373dd19c 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/01.\346\236\266\346\236\204/README.md" @@ -8,6 +8,7 @@ tags: - 架构 permalink: /pages/d9e5d2/ hidden: true +index: false --- # 架构 @@ -76,4 +77,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/00.\350\256\276\350\256\241\346\250\241\345\274\217\346\246\202\350\277\260.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/00.\350\256\276\350\256\241\346\250\241\345\274\217\346\246\202\350\277\260.md" index afe8cf25b4..7fd0e9b752 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/00.\350\256\276\350\256\241\346\250\241\345\274\217\346\246\202\350\277\260.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/00.\350\256\276\350\256\241\346\250\241\345\274\217\346\246\202\350\277\260.md" @@ -1,6 +1,7 @@ --- title: 设计模式概述 date: 2015-01-27 16:50:00 +order: 00 categories: - 设计 - 设计模式 @@ -59,36 +60,42 @@ permalink: /pages/9a2452/ ### 创建型模式代表 -- [单例模式 (Singleton)](单例模式.md) -- [简单工厂模式 (Simple Factory)](简单工厂模式.md) -- [工厂方法模式 (Factory Method)](工厂方法模式.md) -- [抽象工厂模式 (Abstract Factory)](抽象工厂模式.md) -- [建造者模式 (Builder)](建造者模式.md) -- [原型模式 (Prototype)](原型模式.md) +> 创建型模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性。 + +- [简单工厂模式 (Simple Factory)](01.简单工厂模式.md) +- [工厂方法模式 (Factory Method)](02.工厂方法模式.md) +- [抽象工厂模式 (Abstract Factory)](03.抽象工厂模式.md) +- [建造者模式 (Builder)](04.建造者模式.md) +- [原型模式 (Prototype)](05.原型模式.md) +- [单例模式 (Singleton)](06.单例模式.md) ### 结构型模式 -- [适配器模式 (Adapter)](适配器模式.md) -- [桥接模式 (Bridge)](桥接模式.md) -- [装饰模式 (Decorator)](装饰模式.md) -- [组合模式 (Composite)](组合模式.md) -- [外观模式 (Facade)](外观模式.md) -- [享元模式 (Flyweight)](享元模式.md) -- [代理模式 (Proxy)](代理模式.md) +> 结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。 + +- [适配器模式 (Adapter)](07.适配器模式.md) +- [桥接模式 (Bridge)](08.桥接模式.md) +- [组合模式 (Composite)](09.组合模式.md) +- [装饰模式 (Decorator)](10.装饰模式.md) +- [外观模式 (Facade)](11.外观模式.md) +- [享元模式 (Flyweight)](12.享元模式.md) +- [代理模式 (Proxy)](13.代理模式.md) ### 行为型模式 -- [模板方法模式 (Template Method)](模板方法模式.md) -- [命令模式 (Command)](命令模式.md) -- [迭代器模式 (Iterator)](迭代器模式.md) -- [观察者模式 (Observer)](观察者模式.md) -- [解释器模式 (Interpreter)](解释器模式.md) -- [中介者模式 (Mediator)](中介者模式.md) -- [职责链模式 (Chain of Responsibility)](职责链模式.md) -- [备忘录模式 (Memento)](备忘录模式.md) -- [策略模式 (Strategy)](策略模式.md) -- [访问者模式 (Visitor)](访问者模式.md) -- [状态模式 (State)](状态模式.md) +> 行为模式负责对象间的高效沟通和职责委派。 + +- [模板方法模式 (Template Method)](14.模板方法模式.md) +- [命令模式 (Command)](15.命令模式.md) +- [迭代器模式 (Iterator)](16.迭代器模式.md) +- [观察者模式 (Observer)](17.观察者模式.md) +- [解释器模式 (Interpreter)](18.解释器模式.md) +- [中介者模式 (Mediator)](19.中介者模式.md) +- [职责链模式 (Chain of Responsibility)](20.职责链模式.md) +- [备忘录模式 (Memento)](21.备忘录模式.md) +- [策略模式 (Strategy)](22.策略模式.md) +- [访问者模式 (Visitor)](23.访问者模式.md) +- [状态模式 (State)](24.状态模式.md) ## 📚 资料 @@ -97,4 +104,4 @@ permalink: /pages/9a2452/ ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/01.\347\256\200\345\215\225\345\267\245\345\216\202\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/01.\347\256\200\345\215\225\345\267\245\345\216\202\346\250\241\345\274\217.md" index 2ed90a9e07..3d164420c8 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/01.\347\256\200\345\215\225\345\267\245\345\216\202\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/01.\347\256\200\345\215\225\345\267\245\345\216\202\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之简单工厂模式 date: 2015-06-03 09:41:00 +order: 01 categories: - 设计 - 设计模式 @@ -40,7 +41,7 @@ permalink: /pages/ff930b/ 以下通过具体代码来说明。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200724093427.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200724093427.png) 【Product (Operation) 】 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/02.\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/02.\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md" index 280829f202..a5008e3d70 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/02.\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/02.\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之工厂方法模式 date: 2015-06-03 10:03:00 +order: 02 categories: - 设计 - 设计模式 @@ -35,7 +36,7 @@ permalink: /pages/65724c/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210429171651.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210429171651.png) ### 结构说明 @@ -124,7 +125,7 @@ public class factoryMethodPattern { 以下示例演示了如何使用**工厂方法**开发跨平台 UI (用户界面) 组件, 并同时避免客户代码与具体 UI 类之间的耦合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210517194622.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210517194622.png) 基础对话框类使用不同的 UI 组件渲染窗口。 在不同的操作系统下, 这些组件外观或许略有不同, 但其功能保持一致。 Windows 系统中的按钮在 Linux 系统中仍然是按钮。 @@ -219,7 +220,7 @@ class Application is **识别方法:** 工厂方法可通过构建方法来识别, 它会创建具体类的对象, 但以抽象类型或接口的形式返回这些对象。 -还是以 **[简单工厂模式](简单工厂模式.md)** 里的例子来进行说明。 +还是以 **[简单工厂模式](01.简单工厂模式.md)** 里的例子来进行说明。 如何实现一个具有加减乘除基本功能的计算器? diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/03.\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/03.\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217.md" index dfd38b9987..db4f5e2f69 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/03.\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/03.\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之抽象工厂模式 date: 2015-06-03 10:26:00 +order: 03 categories: - 设计 - 设计模式 @@ -42,7 +43,7 @@ permalink: /pages/340aa0/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210517195501.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210517195501.png) ### 结构说明 @@ -178,7 +179,7 @@ ConcreteProductB2 下面例子通过应用**抽象工厂**模式, 使得客户端代码无需与具体 UI 类耦合, 就能创建跨平台的 UI 元素, 同时确保所创建的元素与指定的操作系统匹配。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210517195732.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210517195732.png) 跨平台应用中的相同 UI 元素功能类似, 但是在不同操作系统下的外观有一定差异。 此外, 你需要确保 UI 元素与当前操作系统风格一致。 你一定不希望在 Windows 系统下运行的应用程序中显示 macOS 的控件。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/04.\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/04.\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md" index 9b5e0b4dfc..b4c7e7d8b4 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/04.\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/04.\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之建造者模式 date: 2015-06-03 10:32:00 +order: 04 categories: - 设计 - 设计模式 @@ -24,7 +25,7 @@ permalink: /pages/bf03f3/ 它很好的控制了构建过程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200724105836.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200724105836.png) 建造者模式流程说明: @@ -41,7 +42,7 @@ permalink: /pages/bf03f3/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506090518.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506090518.png) 1. **建造者** (Builder) 接口声明在所有类型建造者中通用的产品构造步骤。 2. **具体建造者** (Concrete Builders) 提供构造过程的不同实现。 具体建造者也可以构造不遵循通用接口的产品。 @@ -155,7 +156,7 @@ public class BuilderPattern { 下面关于**建造者**模式的例子演示了你可以如何复用相同的对象构造代码来生成不同类型的产品——例如汽车 (Car)——及其相应的使用手册 (Manual)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506090759.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506090759.png) ```java // 只有当产品较为复杂且需要详细配置时,使用建造者模式才有意义。下面的两个 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/05.\345\216\237\345\236\213\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/05.\345\216\237\345\236\213\346\250\241\345\274\217.md" index 0c62a89fe3..80701866b2 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/05.\345\216\237\345\236\213\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/05.\345\216\237\345\236\213\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之原型模式 date: 2015-06-03 15:00:00 +order: 05 categories: - 设计 - 设计模式 @@ -36,7 +37,7 @@ permalink: /pages/1af8ee/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506094301.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506094301.png) 1. **原型** (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为 `clone`克隆的方法。 2. **具体原型** (Concrete Prototype) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。 @@ -46,7 +47,7 @@ permalink: /pages/1af8ee/ 在本例中, **原型**模式能让你生成完全相同的几何对象副本, 同时无需代码与对象所属类耦合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210506095002.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210506095002.png) 所有形状类都遵循同一个提供克隆方法的接口。 在复制自身成员变量值到结果对象前, 子类可调用其父类的克隆方法。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/06.\345\215\225\344\276\213\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/06.\345\215\225\344\276\213\346\250\241\345\274\217.md" index 557f327277..2b4a7fefa2 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/06.\345\215\225\344\276\213\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/06.\345\215\225\344\276\213\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之单例模式 date: 2015-06-03 09:24:00 +order: 06 categories: - 设计 - 设计模式 @@ -29,9 +30,9 @@ permalink: /pages/cf046f/ 单例模式的优点: -- ✔ 你可以保证一个类只有一个实例。 -- ✔ 你获得了一个指向该实例的全局访问节点。 -- ✔ 仅在首次请求单例对象时对其进行初始化。 +- ✔️️️ 你可以保证一个类只有一个实例。 +- ✔️️️ 你获得了一个指向该实例的全局访问节点。 +- ✔️️️ 仅在首次请求单例对象时对其进行初始化。 单例模式的缺点: @@ -57,7 +58,7 @@ permalink: /pages/cf046f/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210517200626.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210517200626.png) 1. **单例** (Singleton) 类声明了一个名为 `get­Instance`获取实例的静态方法来返回其所属类的一个相同实例。 - 单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 `获取实例`方法必须是获取单例对象的唯一方式。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/07.\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/07.\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" index 26562a66c4..223b80b90c 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/07.\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/07.\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之适配器模式 date: 2015-01-14 18:31:00 +order: 07 categories: - 设计 - 设计模式 @@ -33,7 +34,7 @@ permalink: /pages/2115cf/ 适配器实现了其中一个对象的接口, 并对另一个对象进行封装。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430141928.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430141928.png) ### 结构说明 @@ -101,7 +102,7 @@ public class AdapterPattern { ## 伪代码 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430165258.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430165258.png) 适配器假扮成一个圆钉 (Round­Peg), 其半径等于方钉 (Square­Peg) 横截面对角线的一半 (即能够容纳方钉的最小外接圆的半径)。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/08.\346\241\245\346\216\245\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/08.\346\241\245\346\216\245\346\250\241\345\274\217.md" index 3e143c7edf..7f46cbee0e 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/08.\346\241\245\346\216\245\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/08.\346\241\245\346\216\245\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之桥接模式 date: 2015-01-16 10:32:00 +order: 08 categories: - 设计 - 设计模式 @@ -28,7 +29,7 @@ permalink: /pages/b05f5f/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430154630.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430154630.png) ### 结构说明 @@ -127,7 +128,7 @@ public class BridgePattern { ## 伪代码 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430170020.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430170020.png) 遥控器基类声明了一个指向设备对象的引用成员变量。 所有遥控器通过通用设备接口与设备进行交互, 使得同一个遥控器可以支持不同类型的设备。 @@ -203,7 +204,7 @@ Java 中桥接模式应用最经典的代表无疑是日志组件 slf4j 的桥 假如,你正在开发应用程序所调用的组件当中已经使用了 common-logging,这时你需要 jcl-over-slf4j.jar 把日志信息输出重定向到 slf4j-api,slf4j-api 再去调用 slf4j 实际依赖的日志组件。这个过程称为桥接。下图是官方的 slf4j 桥接策略图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/log/slf4j-bind-strategy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/log/slf4j-bind-strategy.png) ## 与其他模式的关系 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/09.\347\273\204\345\220\210\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/09.\347\273\204\345\220\210\346\250\241\345\274\217.md" index 48bf3c13f6..05d6e33065 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/09.\347\273\204\345\220\210\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/09.\347\273\204\345\220\210\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之组合模式 date: 2015-01-14 15:33:00 +order: 09 categories: - 设计 - 设计模式 @@ -33,7 +34,7 @@ permalink: /pages/85c0a3/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430162149.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430162149.png) ### 结构说明 @@ -156,7 +157,7 @@ public class CompositePattern { 在本例中, 我们将借助**组合**模式帮助你在图形编辑器中实现一系列的几何图形。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430162653.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430162653.png) `组合图形`Compound­Graphic 是一个容器, 它可以由多个包括容器在内的子图形构成。 组合图形与简单图形拥有相同的方法。 但是, 组合图形自身并不完成具体工作, 而是将请求递归地传递给自己的子项目, 然后 “汇总” 结果。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/10.\350\243\205\351\245\260\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/10.\350\243\205\351\245\260\346\250\241\345\274\217.md" index 211075edcb..e4d9022f0c 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/10.\350\243\205\351\245\260\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/10.\350\243\205\351\245\260\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之装饰模式 date: 2015-01-15 15:41:00 +order: 10 categories: - 设计 - 设计模式 @@ -28,7 +29,7 @@ permalink: /pages/2e24a8/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430172133.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430172133.png) ### 结构说明 @@ -145,7 +146,7 @@ public class DecoratorPattern { ## 伪代码 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430172723.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430172723.png) 在本例中, 装饰模式能够对敏感数据进行压缩和加密, 从而将数据从使用数据的代码中独立出来。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/11.\345\244\226\350\247\202\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/11.\345\244\226\350\247\202\346\250\241\345\274\217.md" index 13935c0447..14a49defe3 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/11.\345\244\226\350\247\202\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/11.\345\244\226\350\247\202\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之外观模式 date: 2015-01-19 15:15:00 +order: 11 categories: - 设计 - 设计模式 @@ -28,7 +29,7 @@ permalink: /pages/ea331b/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430174751.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430174751.png) ### 结构说明 @@ -126,7 +127,7 @@ Facade op1() 在本例中, **外观**模式简化了客户端与复杂视频转换框架之间的交互。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430175224.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430175224.png) 你可以创建一个封装所需功能并隐藏其他代码的外观类, 从而无需使全部代码直接与数十个框架类进行交互。 该结构还能将未来框架升级或更换所造成的影响最小化, 因为你只需修改程序中外观方法的实现即可。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/12.\344\272\253\345\205\203\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/12.\344\272\253\345\205\203\346\250\241\345\274\217.md" index 5a22dab38e..648f1579b6 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/12.\344\272\253\345\205\203\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/12.\344\272\253\345\205\203\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之享元模式 date: 2015-01-19 09:48:00 +order: 12 categories: - 设计 - 设计模式 @@ -22,7 +23,7 @@ permalink: /pages/9147e7/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430182947.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430182947.png) ### 结构说明 @@ -117,7 +118,7 @@ public class FlyweightPattern { ## 伪代码 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430183339.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430183339.png) 在本例中, **享元**模式能有效减少在画布上渲染数百万个树状对象时所需的内存。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/13.\344\273\243\347\220\206\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/13.\344\273\243\347\220\206\346\250\241\345\274\217.md" index 3479fc35da..f9c5b51401 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/13.\344\273\243\347\220\206\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/13.\344\273\243\347\220\206\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之代理模式 date: 2015-01-19 11:38:00 +order: 13 categories: - 设计 - 设计模式 @@ -31,7 +32,7 @@ permalink: /pages/5a865c/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430184301.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430184301.png) ### 结构说明 @@ -81,7 +82,7 @@ class Proxy extends Subject { 本例演示如何使用**代理**模式在第三方腾讯视频 (TencentVideo, 代码示例中记为 TV) 程序库中添加延迟初始化和缓存。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430184525.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430184525.png) 程序库提供了视频下载类。 但是该类的效率非常低。 如果客户端程序多次请求同一视频, 程序库会反复下载该视频, 而不会将首次下载的文件缓存下来复用。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/14.\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/14.\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" index 4a89a39541..191ae0f4d6 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/14.\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/14.\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之模板方法模式 date: 2015-01-06 09:43:00 +order: 14 categories: - 设计 - 设计模式 @@ -37,7 +38,7 @@ permalink: /pages/6eaeb4/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210517201945.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210517201945.png) ### 结构说明 @@ -105,7 +106,7 @@ public class TemplateMethodPattern { 本例中的**模板方法**模式为一款简单策略游戏中人工智能的不同分支提供 “框架”。 -![模板方法模式示例的结构](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210517202308.png) +![模板方法模式示例的结构](https://raw.githubusercontent.com/dunwu/images/master/snap/20210517202308.png) 一款简单游戏的 AI 类。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/15.\345\221\275\344\273\244\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/15.\345\221\275\344\273\244\346\250\241\345\274\217.md" index b9c92d45c2..fb373942c3 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/15.\345\221\275\344\273\244\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/15.\345\221\275\344\273\244\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之命令模式 date: 2015-01-19 17:20:00 +order: 15 categories: - 设计 - 设计模式 @@ -18,7 +19,7 @@ permalink: /pages/22353c/ ### 命令模式的交互 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200726110040.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200726110040.png) - Client 创建一个 ConcreteCommand 对象并指定他的 Receiver 对象。 - 某个 Invoker 对象存储该 ConcreteCommand 对象。 @@ -41,7 +42,7 @@ permalink: /pages/22353c/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210519150619.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519150619.png) ### 结构说明 @@ -130,7 +131,7 @@ public class CommandPattern { 在本例中, **命令**模式会记录已执行操作的历史记录, 以在需要时撤销操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210519151632.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519151632.png) 有些命令会改变编辑器的状态 (例如剪切和粘贴), 它们可在执行相关操作前对编辑器的状态进行备份。 命令执行后会和当前点备份的编辑器状态一起被放入命令历史 (命令对象栈)。 此后, 如果用户需要进行回滚操作, 程序可从历史记录中取出最近的命令, 读取相应的编辑器状态备份, 然后进行恢复。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/16.\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/16.\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md" index 30e314bbb9..5af30b2cc1 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/16.\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/16.\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之迭代器模式 date: 2015-01-19 17:20:00 +order: 16 categories: - 设计 - 设计模式 @@ -24,7 +25,7 @@ permalink: /pages/09d5af/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210519153411.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519153411.png) ### 结构说明 @@ -160,7 +161,7 @@ public class IteratorPattern { 在本例中, **迭代器**模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210519153656.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519153656.png) “好友 (friends)” 迭代器可用于遍历指定档案的好友。 “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/17.\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/17.\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" index 4c2070a9c6..39742d74e4 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/17.\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/17.\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之观察者模式 date: 2015-01-20 16:09:00 +order: 17 categories: - 设计 - 设计模式 @@ -23,7 +24,7 @@ permalink: /pages/056e1d/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210519174232.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519174232.png) ### 结构说明 @@ -160,7 +161,7 @@ public class ObserverPattern { 在本例中, **观察者**模式允许文本编辑器对象将自身的状态改变通知给其他服务对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210519175224.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519175224.png) 订阅者列表是动态生成的: 对象可在运行时根据程序需要开始或停止监听通知。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/18.\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/18.\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217.md" index 7e68bf8c31..4820713836 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/18.\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/18.\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之解释器模式 date: 2015-01-20 18:44:00 +order: 18 categories: - 设计 - 设计模式 @@ -18,7 +19,7 @@ permalink: /pages/48e5aa/ 解释器模式是一种**行为型模式**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200726112138.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200726112138.png) **Context** : 包含解释器之外的一些全局信息。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/19.\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/19.\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md" index 316623e851..76d4d270c3 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/19.\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/19.\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之中介者模式 date: 2015-01-22 13:34:00 +order: 19 categories: - 设计 - 设计模式 @@ -24,7 +25,7 @@ permalink: /pages/3b1f47/ ## 结构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520171152.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520171152.png) ### 结构说明 @@ -143,7 +144,7 @@ public class MediatorPattern { 在本例中, **中介者**模式可帮助你减少各种 UI 类 (按钮、 复选框和文本标签) 之间的相互依赖关系。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520171433.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520171433.png) 用户触发的元素不会直接与其他元素交流, 即使看上去它们应该这样做。 相反, 元素只需让中介者知晓事件即可, 并能在发出通知时同时传递任何上下文信息。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/20.\350\201\214\350\264\243\351\223\276\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/20.\350\201\214\350\264\243\351\223\276\346\250\241\345\274\217.md" index d5af7a0319..44677d3ec1 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/20.\350\201\214\350\264\243\351\223\276\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/20.\350\201\214\350\264\243\351\223\276\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之职责链模式 date: 2015-01-22 14:34:00 +order: 20 categories: - 设计 - 设计模式 @@ -29,7 +30,7 @@ permalink: /pages/b25735/ ### 结构说明 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520172147.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520172147.png) 1. **处理者** (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。 @@ -118,13 +119,13 @@ public class ChainOfResponsibilityPattern { 在本例中, **责任链**模式负责为活动的 GUI 元素显示上下文帮助信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520172324.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520172324.png) 应用程序的 GUI  通常为对象树结构。 例如, 负责渲染程序主窗口的 `对话框`类就是对象树的根节点。 对话框包含 `面板` , 而面板可能包含其他面板, 或是 `按钮`和 `文本框`等下层元素。 只要给一个简单的组件指定帮助文本, 它就可显示简短的上下文提示。 但更复杂的组件可自定义上下文帮助文本的显示方式, 例如显示手册摘录内容或在浏览器中打开一个网页。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520172415.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520172415.png) 当用户将鼠标指针移动到某个元素并按下 `F1`键时, 程序检测到指针下的组件并对其发送帮助请求。 该请求不断向上传递到该元素所有的容器, 直至某个元素能够显示帮助信息。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/21.\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/21.\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md" index 324ad9ca0a..0f5433221a 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/21.\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/21.\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之备忘录模式 date: 2015-01-22 15:26:00 +order: 21 categories: - 设计 - 设计模式 @@ -27,7 +28,7 @@ permalink: /pages/5ae0d5/ #### 基于嵌套类的实现 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520172952.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520172952.png) 1. **原发器** (Originator) 类可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。 @@ -43,7 +44,7 @@ permalink: /pages/5ae0d5/ 另外一种实现方法适用于不支持嵌套类的编程语言 (没错, 我说的就是 PHP)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520173029.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520173029.png) 1. 在没有嵌套类的情况下, 你可以规定负责人仅可通过明确声明的中间接口与备忘录互动, 该接口仅声明与备忘录元数据相关的方法, 限制其对备忘录成员变量的直接访问权限。 2. 另一方面, 原发器可以直接与备忘录对象进行交互, 访问备忘录类中声明的成员变量和方法。 这种方式的缺点在于你需要将备忘录的所有成员变量声明为公有。 @@ -52,7 +53,7 @@ permalink: /pages/5ae0d5/ 如果你不想让其他类有任何机会通过备忘录来访问原发器的状态, 那么还有另一种可用的实现方式。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520173117.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520173117.png) 1. 这种实现方式允许存在多种不同类型的原发器和备忘录。 每种原发器都和其相应的备忘录类进行交互。 原发器和备忘录都不会将其状态暴露给其他类。 2. 负责人此时被明确禁止修改存储在备忘录中的状态。 但负责人类将独立于原发器, 因为此时恢复方法被定义在了备忘录类中。 @@ -155,7 +156,7 @@ State = ON 本例结合使用了[命令](https://refactoringguru.cn/design-patterns/command)模式与备忘录模式, 可保存复杂文字编辑器的状态快照, 并能在需要时从快照中恢复之前的状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520173216.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520173216.png) 命令 (command) 对象将作为负责人, 它们会在执行与命令相关的操作前获取编辑器的备忘录。 当用户试图撤销最近的命令时, 编辑器可以使用保存在命令中的备忘录来将自身回滚到之前的状态。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/22.\347\255\226\347\225\245\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/22.\347\255\226\347\225\245\346\250\241\345\274\217.md" index d1537dc4ce..ca0817d2e5 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/22.\347\255\226\347\225\245\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/22.\347\255\226\347\225\245\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之策略模式 date: 2015-01-22 16:14:00 +order: 22 categories: - 设计 - 设计模式 @@ -27,7 +28,7 @@ permalink: /pages/dc8ecd/ ### 结构说明 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520173840.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520173840.png) 1. **上下文** (Context) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。 2. **策略** (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/23.\350\256\277\351\227\256\350\200\205\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/23.\350\256\277\351\227\256\350\200\205\346\250\241\345\274\217.md" index a8dfbd5138..db02cbbb7c 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/23.\350\256\277\351\227\256\350\200\205\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/23.\350\256\277\351\227\256\350\200\205\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之访问者模式 date: 2015-01-22 18:25:00 +order: 23 categories: - 设计 - 设计模式 @@ -26,7 +27,7 @@ permalink: /pages/671352/ ### 结构说明 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210524103007.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210524103007.png) 1. **访问者** (Visitor) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。 2. **具体访问者** (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。 @@ -152,7 +153,7 @@ ConcreteVisitor2 访问 ConcreteElementB 在本例中, **访问者**模式为几何图像层次结构添加了对于 XML 文件导出功能的支持。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520174801.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520174801.png) ```java // 元素接口声明了一个`accept(接收)`方法,它会将访问者基础接口作为一个参 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/24.\347\212\266\346\200\201\346\250\241\345\274\217.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/24.\347\212\266\346\200\201\346\250\241\345\274\217.md" index 9c5c841051..25f8e108d8 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/24.\347\212\266\346\200\201\346\250\241\345\274\217.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/24.\347\212\266\346\200\201\346\250\241\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 设计模式之状态模式 date: 2015-01-23 10:29:00 +order: 24 categories: - 设计 - 设计模式 @@ -26,7 +27,7 @@ permalink: /pages/d77095/ ### 结构说明 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520175610.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520175610.png) 1. **上下文** (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。 2. **状态** (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。 @@ -109,7 +110,7 @@ public class StatePattern { 在本例中, **状态**模式将根据当前回放状态, 让媒体播放器中的相同控件完成不同的行为。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210520175904.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210520175904.png) 播放器的主要对象总是会连接到一个负责播放器绝大部分工作的状态对象中。 部分操作会更换播放器当前的状态对象, 以此改变播放器对于用户互动所作出的反应。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/25.\351\235\242\345\220\221\345\257\271\350\261\241\345\216\237\345\210\231.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/25.\351\235\242\345\220\221\345\257\271\350\261\241\345\216\237\345\210\231.md" index a738c11ad7..25a6712726 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/25.\351\235\242\345\220\221\345\257\271\350\261\241\345\216\237\345\210\231.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/25.\351\235\242\345\220\221\345\257\271\350\261\241\345\216\237\345\210\231.md" @@ -1,6 +1,7 @@ --- title: 面向对象原则 date: 2021-05-19 09:49:05 +order: 25 categories: - 设计 - 设计模式 diff --git "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/README.md" "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/README.md" index 08c908f47e..ea0f06ef5e 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/02.\350\256\276\350\256\241\346\250\241\345\274\217/README.md" @@ -9,6 +9,7 @@ tags: - 设计模式 permalink: /pages/81b0f2/ hidden: true +index: false --- # 设计模式 @@ -66,4 +67,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/01.\344\273\243\347\240\201\347\232\204\345\235\217\345\221\263\351\201\223\345\222\214\351\207\215\346\236\204.md" "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/01.\344\273\243\347\240\201\347\232\204\345\235\217\345\221\263\351\201\223\345\222\214\351\207\215\346\236\204.md" index b150c94868..44a37c0bf9 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/01.\344\273\243\347\240\201\347\232\204\345\235\217\345\221\263\351\201\223\345\222\214\351\207\215\346\236\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/01.\344\273\243\347\240\201\347\232\204\345\235\217\345\221\263\351\201\223\345\222\214\351\207\215\346\236\204.md" @@ -1,6 +1,7 @@ --- title: 代码的坏味道和重构 date: 2018-10-13 22:48:00 +order: 01 categories: - 设计 - 重构 @@ -124,7 +125,7 @@ _注:功能不全或者不正确,那是残疾代码。就像治病治不了 《重构:改善既有代码的设计》中介绍了 22 种代码的坏味道以及重构手法。这些坏味道可以进一步归类。我总觉得将事物分类有助于理解和记忆。所以本系列将坏味道按照特性分类,然后逐一讲解。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430112053.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430112053.png) ### 代码坏味道之代码臃肿 diff --git "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/02.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\344\273\243\347\240\201\350\207\203\350\202\277.md" "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/02.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\344\273\243\347\240\201\350\207\203\350\202\277.md" index 35fb510ca7..50ae55dab2 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/02.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\344\273\243\347\240\201\350\207\203\350\202\277.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/02.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\344\273\243\347\240\201\350\207\203\350\202\277.md" @@ -1,6 +1,7 @@ --- title: 代码坏味道之代码臃肿 date: 2018-10-13 22:48:00 +order: 02 categories: - 设计 - 重构 @@ -23,7 +24,7 @@ permalink: /pages/49d5ae/ > - 使用常量编码信息(例如一个用于引用管理员权限的常量`USER_ADMIN_ROLE = 1` )。 > - 使用字符串常量作为字段名在数组中使用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/primitive-obsession-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/primitive-obsession-1.png) ### 问题原因 @@ -35,7 +36,7 @@ permalink: /pages/49d5ae/ ### 解决方法 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/primitive-obsession-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/primitive-obsession-2.png) 大多数编程语言都支持基本数据类型和结构类型(类、结构体等)。结构类型允许程序员将基本数据类型组织起来,以代表某一事物的模型。 @@ -52,7 +53,7 @@ permalink: /pages/49d5ae/ - 代码变得更加易读和更加有组织。特殊数据可以集中进行操作,而不像之前那样分散。不用再猜测这些陌生的常量的意义以及它们为什么在数组中。 - 更容易发现重复代码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/primitive-obsession-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/primitive-obsession-3.png) ### 重构方法说明 @@ -62,13 +63,13 @@ permalink: /pages/49d5ae/ 类之中有一个数值类型码,但它并不影响类的行为。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-class-before.png) **解决** 以一个新的类替换该数值类型码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-class-after.png) #### 引入参数对象(Introduce Parameter Object) @@ -76,13 +77,13 @@ permalink: /pages/49d5ae/ 某些参数总是很自然地同时出现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-before.png) **解决** 以一个对象来取代这些参数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-after.png) #### 保持对象完整(Preserve Whole Object) @@ -110,13 +111,13 @@ boolean withinPlan = plan.withinRange(daysTempRange); 你有一个不可变的类型码,它会影响类的行为。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-subclasses-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-subclasses-before.png) **解决** 以子类取代这个类型码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-subclasses-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-subclasses-after.png) #### 以状态/策略模式取代类型码(Replace Type Code with State/Strategy) @@ -124,13 +125,13 @@ boolean withinPlan = plan.withinRange(daysTempRange); 你有一个类型码,它会影响类的行为,但你无法通过继承消除它。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-state-strategy-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-state-strategy-before.png) **解决** 以状态对象取代类型码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-state-strategy-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-state-strategy-after.png) #### 以对象取代数组(Replace Array with Object) @@ -160,7 +161,7 @@ row.setWins("15"); > > 有时,代码的不同部分包含相同的变量组(例如用于连接到数据库的参数)。这些绑在一起出现的数据应该拥有自己的对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/data-clumps-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/data-clumps-1.png) ### 问题原因 @@ -180,7 +181,7 @@ row.setWins("15"); - 提高代码易读性和组织性。对于特殊数据的操作,可以集中进行处理,而不像以前那样分散。 - 减少代码量。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/data-clumps-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/data-clumps-3.png) ### 何时忽略 @@ -194,13 +195,13 @@ row.setWins("15"); 某个类做了不止一件事。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-before.png) **解决** 建立一个新类,将相关的字段和函数从旧类搬移到新类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-after.png) #### 引入参数对象(Introduce Parameter Object) @@ -208,13 +209,13 @@ row.setWins("15"); 某些参数总是很自然地同时出现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-before.png) **解决** 以一个对象来取代这些参数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-after.png) #### 保持对象完整(Preserve Whole Object) @@ -242,7 +243,7 @@ boolean withinPlan = plan.withinRange(daysTempRange); > > 一个类含有过多字段、函数、代码行。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/large-class-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/large-class-1.png) ### 问题原因 @@ -254,7 +255,7 @@ boolean withinPlan = plan.withinRange(daysTempRange); 设计模式中有一条重要原则:职责单一原则。一个类应该只赋予它一个职责。如果它所承担的职责太多,就该考虑为它减减负。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/large-class-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/large-class-2.png) - 如果过大类中的部分行为可以提炼到一个独立的组件中,可以使用 `提炼类(Extract Class)`。 - 如果过大类中的部分行为可以用不同方式实现或使用于特殊场景,可以使用 `提炼子类(Extract Subclass)`。 @@ -266,7 +267,7 @@ boolean withinPlan = plan.withinRange(daysTempRange); - 重构过大的类可以使程序员不必记住一个类中大量的属性。 - 在大多数情况下,分割过大的类可以避免代码和功能的重复。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/large-class-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/large-class-3.png) ### 重构方法说明 @@ -276,13 +277,13 @@ boolean withinPlan = plan.withinRange(daysTempRange); 某个类做了不止一件事。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-before.png) **解决** 建立一个新类,将相关的字段和函数从旧类搬移到新类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-after.png) #### 提炼子类(Extract Subclass) @@ -290,13 +291,13 @@ boolean withinPlan = plan.withinRange(daysTempRange); 一个类中有些特性仅用于特定场景。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-subclass-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-subclass-before.png) **解决** 创建一个子类,并将用于特殊场景的特性置入其中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-subclass-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-subclass-after.png) #### 提炼接口(Extract Interface) @@ -304,13 +305,13 @@ boolean withinPlan = plan.withinRange(daysTempRange); 多个客户端使用一个类部分相同的函数。另一个场景是两个类中的部分函数相同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-interface-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-interface-before.png) **解决** 移动相同的部分函数到接口中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-interface-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-interface-after.png) #### 复制被监视数据(Duplicate Observed Data) @@ -318,13 +319,13 @@ boolean withinPlan = plan.withinRange(daysTempRange); 如果存储在类中的数据是负责 GUI 的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/duplicate-observed-data-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/duplicate-observed-data-before.png) **解决** 一个比较好的方法是将负责 GUI 的数据放入一个独立的类,以确保 GUI 数据与域类之间的连接和同步。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/duplicate-observed-data-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/duplicate-observed-data-after.png) ## 过长函数 @@ -333,7 +334,7 @@ boolean withinPlan = plan.withinRange(daysTempRange); > 一个函数含有太多行代码。一般来说,任何函数超过 10 行时,你就可以考虑是不是过长了。 > 函数中的代码行数原则上不要超过 100 行。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/long-method-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/long-method-1.png) ### 问题的原因 @@ -344,7 +345,7 @@ boolean withinPlan = plan.withinRange(daysTempRange); 一个很好的技巧是:**寻找注释**。添加注释,一般有这么几个原因:代码逻辑较为晦涩或复杂;这段代码功能相对独立;特殊处理。 如果代码前方有一行注释,就是在提醒你:可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名。如果函数有一个描述恰当的名字,就不需要去看内部代码究竟是如何实现的。就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/long-method-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/long-method-2.png) - 为了给一个函数瘦身,可以使用 `提炼函数(Extract Method)`。 - 如果局部变量和参数干扰提炼函数,可以使用 `以查询取代临时变量(Replace Temp with Query)`,`引入参数对象(Introduce Parameter Object)` 或 `保持对象完整(Preserve Whole Object)` 。 @@ -356,7 +357,7 @@ boolean withinPlan = plan.withinRange(daysTempRange); - 在所有类型的面向对象代码中,函数比较短小精悍的类往往生命周期较长。一个函数越长,就越不容易理解和维护。 - 此外,过长函数中往往含有难以发现的重复代码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/long-method-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/long-method-3.png) ### 性能 @@ -439,13 +440,13 @@ double basePrice() { 某些参数总是很自然地同时出现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-before.png) **解决** 以一个对象来取代这些参数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-after.png) #### 保持对象完整(Preserve Whole Object) @@ -549,7 +550,7 @@ else { > > 一个函数有超过 3、4 个入参。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/long-parameter-list-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/long-parameter-list-1.png) ### 问题原因 @@ -561,7 +562,7 @@ else { ### 解决方案 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/long-parameter-list-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/long-parameter-list-2.png) - 如果向已有的对象发出一条请求就可以取代一个参数,那么你应该使用 `以函数取代参数(Replace Parameter with Methods)` 。在这里,,“已有的对象”可能是函数所属类里的一个字段,也可能是另一个参数。 - 你还可以运用 `保持对象完整(Preserve Whole Object)` 将来自同一对象的一堆数据收集起来,并以该对象替换它们。 @@ -626,13 +627,13 @@ boolean withinPlan = plan.withinRange(daysTempRange); 某些参数总是很自然地同时出现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-before.png) **解决** 以一个对象来取代这些参数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-parameter-object-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-parameter-object-after.png) ## 扩展阅读 diff --git "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/03.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\346\273\245\347\224\250\351\235\242\345\220\221\345\257\271\350\261\241.md" "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/03.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\346\273\245\347\224\250\351\235\242\345\220\221\345\257\271\350\261\241.md" index d3dab47e5f..2bcfe63b35 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/03.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\346\273\245\347\224\250\351\235\242\345\220\221\345\257\271\350\261\241.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/03.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\346\273\245\347\224\250\351\235\242\345\220\221\345\257\271\350\261\241.md" @@ -1,6 +1,7 @@ --- title: 代码坏味道之滥用面向对象 date: 2018-10-13 22:48:00 +order: 03 categories: - 设计 - 重构 @@ -20,7 +21,7 @@ permalink: /pages/65ee05/ > > 你有一个复杂的 `switch` 语句或 `if` 序列语句。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/switch-statements-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/switch-statements-1.png) ### 问题原因 @@ -40,7 +41,7 @@ permalink: /pages/65ee05/ - 提升代码组织性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/switch-statements-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/switch-statements-2.png) ### 何时忽略 @@ -87,13 +88,13 @@ void printDetails(double outstanding) { 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 以子类取代类型码(Replace Type Code with Subclass) @@ -101,13 +102,13 @@ void printDetails(double outstanding) { 你有一个不可变的类型码,它会影响类的行为。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-subclasses-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-subclasses-before.png) **解决** 以子类取代这个类型码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-subclasses-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-subclasses-after.png) #### 以状态/策略模式取代类型码(Replace Type Code with State/Strategy) @@ -115,13 +116,13 @@ void printDetails(double outstanding) { 你有一个类型码,它会影响类的行为,但你无法通过继承消除它。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-state-strategy-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-state-strategy-before.png) **解决** 以状态对象取代类型码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-type-code-with-state-strategy-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-type-code-with-state-strategy-after.png) #### 以多态取代条件表达式(Replace Conditional with Polymorphism) @@ -247,7 +248,7 @@ plan = customer.getPlan(); > 临时字段(Temporary Field)的值只在特定环境下有意义,离开这个环境,它们就什么也不是了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/temporary-field-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/temporary-field-1.png) ### 问题原因 @@ -260,13 +261,13 @@ plan = customer.getPlan(); - 可以通过 `提炼类(Extract Class)` 将临时字段和操作它们的所有代码提炼到一个单独的类中。此外,你可以运用 `以函数对象取代函数(Replace Method with Method Object)` 来实现同样的目的。 - `引入 Null 对象(Introduce Null Object)` 在“变量不合法”的情况下创建一个 null 对象,从而避免写出条件表达式。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/temporary-field-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/temporary-field-2.png) ### 收益 - 更好的代码清晰度和组织性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/temporary-field-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/temporary-field-3.png) ### 重构方法说明 @@ -276,13 +277,13 @@ plan = customer.getPlan(); 某个类做了不止一件事。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-before.png) **解决** 建立一个新类,将相关的字段和函数从旧类搬移到新类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-after.png) #### 以函数对象取代函数(Replace Method with Method Object) @@ -372,7 +373,7 @@ plan = customer.getPlan(); > > 两个类中有着不同的函数,却在做着同一件事。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/alternative-classes-with-different-interfaces-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/alternative-classes-with-different-interfaces-1.png) ### 问题原因 @@ -390,7 +391,7 @@ plan = customer.getPlan(); - 消除了不必要的重复代码,为代码瘦身了。 - 代码更易读(不再需要猜测为什么要有两个功能相同的类)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/alternative-classes-with-different-interfaces-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/alternative-classes-with-different-interfaces-2.png) ### 何时忽略 @@ -426,13 +427,13 @@ class Person { 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 添加参数(Add Parameter) @@ -460,12 +461,12 @@ class Customer { 若干函数做了类似的工作,但在函数本体中却包含了不同的值。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/parameterize-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/parameterize-method-before.png) **解决** 建立单一函数,以参数表达哪些不同的值。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/parameterize-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/parameterize-method-after.png) #### 提炼超类(Extract Superclass) @@ -473,13 +474,13 @@ class Customer { 两个类有相似特性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-superclass-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-before.png) **解决** 为这两个类建立一个超类,将相同特性移至超类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-superclass-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-after.png) ## 被拒绝的馈赠 @@ -487,7 +488,7 @@ class Customer { > > 子类仅仅使用父类中的部分方法和属性。其他来自父类的馈赠成为了累赘。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/refused-bequest-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/refused-bequest-1.png) ### 问题原因 @@ -498,13 +499,13 @@ class Customer { - 如果继承没有意义并且子类和父类之间确实没有共同点,可以运用 `以委托取代继承(Replace Inheritance with Delegation)` 消除继承。 - 如果继承是适当的,则去除子类中不需要的字段和方法。运用 `提炼超类(Extract Superclass)` 将所有超类中对于子类有用的字段和函数提取出来,置入一个新的超类中,然后让两个类都继承自它。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/refused-bequest-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/refused-bequest-2.png) ### 收益 - 提高代码的清晰度和组织性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/refused-bequest-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/refused-bequest-3.png) ### 重构方法说明 @@ -514,7 +515,7 @@ class Customer { 某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-inheritance-with-delegation-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-inheritance-with-delegation-before.png) **解决** @@ -522,7 +523,7 @@ class Customer { 2. 调整子类函数,令它改而委托超类; 3. 然后去掉两者之间的继承关系。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-inheritance-with-delegation-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-inheritance-with-delegation-after.png) #### 提炼超类(Extract Superclass) @@ -530,13 +531,13 @@ class Customer { 两个类有相似特性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-superclass-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-before.png) **解决** 为这两个类建立一个超类,将相同特性移至超类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-superclass-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-after.png) ## 扩展阅读 diff --git "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/04.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\345\217\230\351\235\251\347\232\204\351\232\234\347\242\215.md" "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/04.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\345\217\230\351\235\251\347\232\204\351\232\234\347\242\215.md" index 20170efef6..3f42411d65 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/04.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\345\217\230\351\235\251\347\232\204\351\232\234\347\242\215.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/04.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\345\217\230\351\235\251\347\232\204\351\232\234\347\242\215.md" @@ -1,6 +1,7 @@ --- title: 代码坏味道之变革的障碍 date: 2018-10-13 22:48:00 +order: 04 categories: - 设计 - 重构 @@ -23,7 +24,7 @@ permalink: /pages/56ca63/ 你发现你想要修改一个函数,却必须要同时修改许多不相关的函数。例如,当你想要添加一个新的产品类型时,你需要同步修改对产品进行查找、显示、排序的函数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/divergent-change-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/divergent-change-1.png) ### 问题原因 @@ -46,13 +47,13 @@ permalink: /pages/56ca63/ 某个类做了不止一件事。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-before.png) **解决** 建立一个新类,将相关的字段和函数从旧类搬移到新类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-after.png) ## 平行继承体系 @@ -62,7 +63,7 @@ permalink: /pages/56ca63/ 每当你为某个类添加一个子类,必须同时为另一个类相应添加一个子类。这种情况的典型特征是:某个继承体系的类名前缀或类名后缀完全相同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/parallel-inheritance-hierarchies-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/parallel-inheritance-hierarchies-1.png) ### 问题原因 @@ -89,13 +90,13 @@ permalink: /pages/56ca63/ 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 搬移字段(Move Field) @@ -103,13 +104,13 @@ permalink: /pages/56ca63/ 在你的程序中,某个字段被其所驻类之外的另一个类更多地用到。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-field-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-field-before.png) **解决** 在目标类新建一个字段,修改源字段的所有用户,令他们改用新字段。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-field-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-field-after.png) ## 霰弹式修改 @@ -119,7 +120,7 @@ permalink: /pages/56ca63/ 任何修改都需要在许多不同类上做小幅度修改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/shotgun-surgery-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/shotgun-surgery-1.png) ### 问题原因 @@ -130,7 +131,7 @@ permalink: /pages/56ca63/ - 运用`搬移函数(Move Method)` 和 `搬移字段(Move Field)` 来搬移不同类中相同的行为到一个独立类中。如果没有适合存放搬移函数或字段的类,就创建一个新类。 - 通常,可以运用 `将类内联化(Inline Class)` 将一些列相关行为放进同一个类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/shotgun-surgery-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/shotgun-surgery-2.png) ### 收益 @@ -138,7 +139,7 @@ permalink: /pages/56ca63/ - 减少重复代码 - 更易维护 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/shotgun-surgery-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/shotgun-surgery-3.png) ### 重构方法说明 @@ -148,13 +149,13 @@ permalink: /pages/56ca63/ 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 搬移字段(Move Field) @@ -162,13 +163,13 @@ permalink: /pages/56ca63/ 在你的程序中,某个字段被其所驻类之外的另一个类更多地用到。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-field-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-field-before.png) **解决** 在目标类新建一个字段,修改源字段的所有用户,令他们改用新字段。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-field-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-field-after.png) #### 将类内联化(Inline Class) @@ -176,13 +177,13 @@ permalink: /pages/56ca63/ 某个类没有做太多事情。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inline-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inline-class-before.png) **解决** 将这个类的所有特性搬移到另一个类中,然后移除原类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inline-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inline-class-after.png) ## 扩展阅读 diff --git "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/05.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\351\235\236\345\277\205\350\246\201\347\232\204.md" "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/05.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\351\235\236\345\277\205\350\246\201\347\232\204.md" index 41f330a9ab..35724f1751 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/05.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\351\235\236\345\277\205\350\246\201\347\232\204.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/05.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\351\235\236\345\277\205\350\246\201\347\232\204.md" @@ -1,6 +1,7 @@ --- title: 代码坏味道之非必要的 date: 2018-10-13 22:48:00 +order: 05 categories: - 设计 - 重构 @@ -21,7 +22,7 @@ permalink: /pages/47acb5/ > > 理解和维护总是费时费力的。如果一个类不值得你花费精力,它就应该被删除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/lazy-class-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/lazy-class-1.png) ### 问题原因 @@ -32,7 +33,7 @@ permalink: /pages/47acb5/ - 没什么用的类可以运用 `将类内联化(Inline Class)` 来干掉。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/lazy-class-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/lazy-class-2.png) - 如果子类用处不大,试试 `折叠继承体系(Collapse Hierarchy)` 。 @@ -53,13 +54,13 @@ permalink: /pages/47acb5/ 某个类没有做太多事情。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inline-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inline-class-before.png) **解决** 将这个类的所有特性搬移到另一个类中,然后移除原类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inline-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inline-class-after.png) #### 折叠继承体系(Collapse Hierarchy) @@ -67,13 +68,13 @@ permalink: /pages/47acb5/ 超类和子类之间无太大区别。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/collapse-hierarchy-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/collapse-hierarchy-before.png) **解决** 将它们合为一体。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/collapse-hierarchy-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/collapse-hierarchy-after.png) ## 夸夸其谈未来性 @@ -81,7 +82,7 @@ permalink: /pages/47acb5/ > > 存在未被使用的类、函数、字段或参数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/speculative-generality-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/speculative-generality-1.png) ### 问题原因 @@ -91,7 +92,7 @@ permalink: /pages/47acb5/ - 如果你的某个抽象类其实没有太大作用,请运用 `折叠继承体系(Collapse Hierarch)` 。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/speculative-generality-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/speculative-generality-2.png) - 不必要的委托可运用 `将类内联化(Inline Class)` 消除。 - 无用的函数可运用 `内联函数(Inline Method)` 消除。 @@ -116,13 +117,13 @@ permalink: /pages/47acb5/ 超类和子类之间无太大区别。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/collapse-hierarchy-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/collapse-hierarchy-before.png) **解决** 将它们合为一体。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/collapse-hierarchy-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/collapse-hierarchy-after.png) #### 将类内联化(Inline Class) @@ -130,13 +131,13 @@ permalink: /pages/47acb5/ 某个类没有做太多事情。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inline-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inline-class-before.png) **解决** 将这个类的所有特性搬移到另一个类中,然后移除原类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inline-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inline-class-after.png) #### 内联函数(Inline Method) @@ -175,19 +176,19 @@ class PizzaDelivery { 函数本体不再需要某个参数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/remove-parameter-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/remove-parameter-before.png) **解决** 将该参数去除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/remove-parameter-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/remove-parameter-after.png) ## 纯稚的数据类 > `纯稚的数据类(Data Class)` 指的是只包含字段和访问它们的 getter 和 setter 函数的类。这些仅仅是供其他类使用的数据容器。这些类不包含任何附加功能,并且不能对自己拥有的数据进行独立操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/data-class-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/data-class-1.png) ### 问题原因 @@ -199,7 +200,7 @@ class PizzaDelivery { - 如果这些类含容器类的字段,你应该检查它们是不是得到了恰当的封装;如果没有,就运用 `封装集合(Encapsulated Collection)` 把它们封装起来。 - 找出这些 getter/setter 函数被其他类运用的地点。尝试以 `搬移函数(Move Method)` 把那些调用行为搬移到 `纯稚的数据类(Data Class)` 来。如果无法搬移这个函数,就运用 `提炼函数(Extract Method)` 产生一个可搬移的函数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/data-class-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/data-class-2.png) - 在类已经充满了深思熟虑的函数之后,你可能想要摆脱旧的数据访问方法,以提供适应面较广的类数据访问接口。为此,可以运用 `移除设置函数(Remove Setting Method)` 和 `隐藏函数(Hide Method)` 。 @@ -245,13 +246,13 @@ class Person { 有个函数返回一个集合。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/encapsulate-collection-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/encapsulate-collection-before.png) **解决** 让该函数返回该集合的一个只读副本,并在这个类中提供添加、移除集合元素的函数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/encapsulate-collection-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/encapsulate-collection-after.png) #### 搬移函数(Move Method) @@ -259,13 +260,13 @@ class Person { 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 提炼函数(Extract Method) @@ -305,13 +306,13 @@ void printDetails(double outstanding) { 类中的某个字段应该在对象创建时被设值,然后就不再改变。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/remove-setting-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/remove-setting-method-before.png) **解决** 去掉该字段的所有设值函数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/remove-setting-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/remove-setting-method-after.png) #### 隐藏函数(Hide Method) @@ -319,13 +320,13 @@ void printDetails(double outstanding) { 有一个函数,从来没有被其他任何类用到。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/hide-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/hide-method-before.png) **解决** 将这个函数修改为 private。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/hide-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/hide-method-after.png) ## 过多的注释 @@ -333,7 +334,7 @@ void printDetails(double outstanding) { > > 注释本身并不是坏事。但是常常有这样的情况:一段代码中出现长长的注释,而它之所以存在,是因为代码很糟糕。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/comments-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/comments-1.png) ### 问题原因 @@ -488,7 +489,7 @@ _注:请不要滥用断言。不要使用它来检查”应该为真“的条 > > 重复代码堪称为代码坏味道之首。消除重复代码总是有利无害的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/duplicate-code-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/duplicate-code-1.png) ### 问题原因 @@ -502,7 +503,7 @@ _注:请不要滥用断言。不要使用它来检查”应该为真“的条 - 同一个类的两个函数含有相同的表达式,这时可以采用 `提炼函数(Extract Method)` 提炼出重复的代码,然后让这两个地点都调用被提炼出来的那段代码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/duplicate-code-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/duplicate-code-2.png) - 如果两个互为兄弟的子类含有重复代码: - 首先对两个类都运用 `提炼函数(Extract Method)` ,然后对被提炼出来的函数运用 `函数上移(Pull Up Method)` ,将它推入超类。 @@ -560,13 +561,13 @@ void printDetails(double outstanding) { 有些函数,在各个子类中产生完全相同的结果。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/pull-up-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/pull-up-method-before.png) **解决** 将该函数移至超类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/pull-up-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/pull-up-method-after.png) #### 构造函数本体上移(Pull Up Constructor Body) @@ -605,13 +606,13 @@ class Manager extends Employee { 你有一些子类,其中相应的某些函数以相同的顺序执行类似的操作,但各个操作的细节上有所不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/form-template-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/form-template-method-before.png) **解决** 将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/form-template-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/form-template-method-after.png) _注:这里只提到具体做法,建议了解一下模板方法设计模式。_ @@ -661,13 +662,13 @@ String foundPerson(String[] people){ 两个类有相似特性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-superclass-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-before.png) **解决** 为这两个类建立一个超类,将相同特性移至超类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-superclass-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-superclass-after.png) #### 提炼类(Extract Class) @@ -675,13 +676,13 @@ String foundPerson(String[] people){ 某个类做了不止一件事。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-before.png) **解决** 建立一个新类,将相关的字段和函数从旧类搬移到新类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-after.png) #### 合并条件表达式(Consolidate Conditional Expression) diff --git "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/06.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\350\200\246\345\220\210.md" "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/06.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\350\200\246\345\220\210.md" index ea6269367a..d846c8bd25 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/06.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\350\200\246\345\220\210.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/06.\344\273\243\347\240\201\345\235\217\345\221\263\351\201\223\344\271\213\350\200\246\345\220\210.md" @@ -1,6 +1,7 @@ --- title: 代码坏味道之耦合 date: 2018-10-13 22:48:00 +order: 06 categories: - 设计 - 重构 @@ -80,13 +81,13 @@ class Report { 你需要为服务类提供一些额外函数,但你无法修改这个类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-local-extension-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-local-extension-before.png) **解决** 建立一个新类,使它包含这些额外函数,让这个扩展品成为源类的子类或包装类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/introduce-local-extension-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/introduce-local-extension-after.png) ## 中间人 @@ -94,7 +95,7 @@ class Report { > > 如果一个类的作用仅仅是指向另一个类的委托,为什么要存在呢? -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/middle-man-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/middle-man-1.png) ### 问题原因 @@ -108,7 +109,7 @@ class Report { - 减少笨重的代码。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/middle-man-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/middle-man-2.png) ### 何时忽略 @@ -125,13 +126,13 @@ class Report { 某个类做了过多的简单委托动作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/remove-middle-man-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/remove-middle-man-before.png) **解决** 让客户直接调用委托类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/remove-middle-man-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/remove-middle-man-after.png) ## 依恋情结 @@ -139,7 +140,7 @@ class Report { > > 一个函数访问其它对象的数据比访问自己的数据更多。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/feature-envy-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/feature-envy-1.png) ### 问题原因 @@ -151,7 +152,7 @@ As a basic rule, if things change at the same time, you should keep them in the 有一个基本原则:同时会发生改变的事情应该被放在同一个地方。通常,数据和使用这些数据的函数是一起改变的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/feature-envy-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/feature-envy-2.png) - 如果一个函数明显应该被移到另一个地方,可运用 `搬移函数(Move Method)` 。 - 如果仅仅是函数的部分代码访问另一个对象的数据,运用 `提炼函数(Extract Method)` 将这部分代码移到独立的函数中。 @@ -162,7 +163,7 @@ As a basic rule, if things change at the same time, you should keep them in the - 减少重复代码(如果数据处理的代码放在中心位置)。 - 更好的代码组织性(处理数据的函数靠近实际数据)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/feature-envy-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/feature-envy-3.png) ### 何时忽略 @@ -176,13 +177,13 @@ As a basic rule, if things change at the same time, you should keep them in the 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 提炼函数(Extract Method) @@ -222,7 +223,7 @@ void printDetails(double outstanding) { > > 一个类大量使用另一个类的内部字段和方法。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inappropriate-intimacy-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inappropriate-intimacy-1.png) ### 问题原因 @@ -232,7 +233,7 @@ void printDetails(double outstanding) { - 最简单的解决方法是运用 `搬移函数(Move Method)` 和 `搬移字段(Move Field)` 来让类之间斩断羁绊。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inappropriate-intimacy-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inappropriate-intimacy-2.png) - 你也可以看看是否能运用 `将双向关联改为单向关联(Change Bidirectional Association to Unidirectional)` 让其中一个类对另一个说分手。 @@ -244,7 +245,7 @@ void printDetails(double outstanding) { - 提高代码组织性。 - 提高代码复用性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/inappropriate-intimacy-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/inappropriate-intimacy-3.png) ### 重构方法说明 @@ -254,13 +255,13 @@ void printDetails(double outstanding) { 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) #### 搬移字段(Move Field) @@ -268,13 +269,13 @@ void printDetails(double outstanding) { 在你的程序中,某个字段被其所驻类之外的另一个类更多地用到。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-field-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-field-before.png) **解决** 在目标类新建一个字段,修改源字段的所有用户,令他们改用新字段。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-field-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-field-after.png) #### 将双向关联改为单向关联(Change Bidirectional Association to Unidirectional) @@ -282,13 +283,13 @@ void printDetails(double outstanding) { 两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/change-bidirectional-association-to-unidirectional-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/change-bidirectional-association-to-unidirectional-before.png) **解决** 去除不必要的关联。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/change-bidirectional-association-to-unidirectional-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/change-bidirectional-association-to-unidirectional-after.png) #### 提炼类(Extract Class) @@ -296,13 +297,13 @@ void printDetails(double outstanding) { 某个类做了不止一件事。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-before.png) **解决** 建立一个新类,将相关的字段和函数从旧类搬移到新类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/extract-class-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/extract-class-after.png) #### 隐藏委托关系(Hide Delegate) @@ -310,13 +311,13 @@ void printDetails(double outstanding) { 客户通过一个委托类来调用另一个对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/hide-delegate-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/hide-delegate-before.png) **解决** 在服务类上建立客户所需的所有函数,用以隐藏委托关系。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/hide-delegate-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/hide-delegate-after.png) #### 以委托取代继承(Replace Inheritance with Delegation) @@ -324,13 +325,13 @@ void printDetails(double outstanding) { 某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-delegation-with-inheritance-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-delegation-with-inheritance-before.png) **解决** 在子类中新建一个字段用以保存超类;调整子类函数,令它改而委托超类;然后去掉两者之间的继承关系。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/replace-delegation-with-inheritance-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/replace-delegation-with-inheritance-after.png) ## 过度耦合的消息链 @@ -338,7 +339,7 @@ void printDetails(double outstanding) { > > 消息链的形式类似于:`obj.getA().getB().getC()`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/message-chains-1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/message-chains-1.png) ### 问题原因 @@ -348,7 +349,7 @@ void printDetails(double outstanding) { - 可以运用 `隐藏委托关系(Hide Delegate)` 删除一个消息链。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/message-chains-2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/message-chains-2.png) - 有时更好的选择是:先观察消息链最终得到的对象是用来干什么的。看看能否以 `提炼函数(Extract Method)`把使用该对象的代码提炼到一个独立函数中,再运用 `搬移函数(Move Method)` 把这个函数推入消息链。 @@ -357,7 +358,7 @@ void printDetails(double outstanding) { - 能减少链中类之间的依赖。 - 能减少代码量。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/message-chains-3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/message-chains-3.png) ### 何时忽略 @@ -371,13 +372,13 @@ void printDetails(double outstanding) { 客户通过一个委托类来调用另一个对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/hide-delegate-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/hide-delegate-before.png) **解决** 在服务类上建立客户所需的所有函数,用以隐藏委托关系。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/hide-delegate-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/hide-delegate-after.png) #### 提炼函数(Extract Method) @@ -417,13 +418,13 @@ void printDetails(double outstanding) { 你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-before.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-before.png) **解决** 在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是旧函数完全移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/refactor/move-method-after.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/refactor/move-method-after.png) ## 扩展阅读 diff --git "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/README.md" "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/README.md" index 5dfe86f036..fb07281a76 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/03.\351\207\215\346\236\204/README.md" @@ -9,6 +9,7 @@ tags: - 重构 permalink: /pages/d200c3/ hidden: true +index: false --- # 重构 @@ -17,7 +18,7 @@ hidden: true > > **重构的目的是为了提高代码的质量和性能**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210430112157.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210430112157.png) ## 📖 内容 @@ -37,4 +38,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/04.DDD/01.\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\347\256\200\344\273\213.md" "b/source/_posts/03.\350\256\276\350\256\241/04.DDD/01.\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\347\256\200\344\273\213.md" index a602b21587..8372f826bf 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/04.DDD/01.\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\347\256\200\344\273\213.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/04.DDD/01.\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: 领域驱动设计简介 date: 2020-08-10 10:59:18 +order: 01 categories: - 设计 - DDD @@ -41,7 +42,7 @@ DDD 主要关注:**从业务领域视角划分领域边界**,构建通用语 ## DDD 核心概念 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719231154.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719231154.png) ### 域 @@ -75,7 +76,7 @@ DDD 主要关注:**从业务领域视角划分领域边界**,构建通用语 聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719152031.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719152031.png) #### 聚合设计步骤 @@ -100,7 +101,7 @@ DDD 主要关注:**从业务领域视角划分领域边界**,构建通用语 ### DDD 架构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719223353.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719223353.png) 三层架构向 DDD 分层架构演进,主要发生在业务逻辑层和数据访问层。 @@ -112,7 +113,7 @@ DDD 分层架构包含用户接口层、应用层、领域层和基础层。通 整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719223906.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719223906.png) ### 六边形架构 @@ -130,7 +131,7 @@ DDD 分层架构包含用户接口层、应用层、领域层和基础层。通 这三种架构模型的设计思想正是微服务架构高内聚低耦合原则的完美体现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719224313.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719224313.png) #### 架构模型和中台、微服务的联系 @@ -167,7 +168,7 @@ DDD、中台、微服务这三者之间似乎没什么关联,实际上它们 简单的理解就是把传统的前后台体系中的后台进行了细分。阿里巴巴提出了**大中台小前台**的战略。就是强化业务和技术中台,把前端的应用变得更小更灵活。当中台越强大,能力就越强,越能更好的快速响应前台的业务需求。打个比喻,就是土壤越肥沃,越适合生长不同的生物,打造好的生态系统。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716194609.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200716194609.png) ### 什么是中台 @@ -225,7 +226,7 @@ DDD、中台、微服务这三者之间似乎没什么关联,实际上它们 第一步:锁定系统所在业务域,构建领域模型。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200720063540.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200720063540.png) 第二步:对齐业务域,构建中台业务模型。 diff --git "a/source/_posts/03.\350\256\276\350\256\241/04.DDD/README.md" "b/source/_posts/03.\350\256\276\350\256\241/04.DDD/README.md" index 5501118e86..8a8dba0537 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/04.DDD/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/04.DDD/README.md" @@ -9,6 +9,7 @@ tags: - DDD permalink: /pages/833925/ hidden: true +index: false --- # 领域驱动设计 @@ -23,4 +24,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/11.UML/01.UML\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/03.\350\256\276\350\256\241/05.UML/01.UML\345\277\253\351\200\237\345\205\245\351\227\250.md" similarity index 98% rename from "source/_posts/03.\350\256\276\350\256\241/11.UML/01.UML\345\277\253\351\200\237\345\205\245\351\227\250.md" rename to "source/_posts/03.\350\256\276\350\256\241/05.UML/01.UML\345\277\253\351\200\237\345\205\245\351\227\250.md" index db388c6a9a..57692eb8b5 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/11.UML/01.UML\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/05.UML/01.UML\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: UML 快速入门 date: 2019-03-06 00:00:00 +order: 01 categories: - 设计 - UML @@ -18,7 +19,7 @@ permalink: /pages/ae1396/ UML 图类型如下图所示: -
+
#### [结构式建模图](02.UML结构建模图.md) diff --git "a/source/_posts/03.\350\256\276\350\256\241/11.UML/02.UML\347\273\223\346\236\204\345\273\272\346\250\241\345\233\276.md" "b/source/_posts/03.\350\256\276\350\256\241/05.UML/02.UML\347\273\223\346\236\204\345\273\272\346\250\241\345\233\276.md" similarity index 88% rename from "source/_posts/03.\350\256\276\350\256\241/11.UML/02.UML\347\273\223\346\236\204\345\273\272\346\250\241\345\233\276.md" rename to "source/_posts/03.\350\256\276\350\256\241/05.UML/02.UML\347\273\223\346\236\204\345\273\272\346\250\241\345\233\276.md" index 01a4fe4c87..c77daea0b8 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/11.UML/02.UML\347\273\223\346\236\204\345\273\272\346\250\241\345\233\276.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/05.UML/02.UML\347\273\223\346\236\204\345\273\272\346\250\241\345\233\276.md" @@ -1,6 +1,7 @@ --- title: UML 结构建模图 date: 2018-10-26 17:39:34 +order: 02 categories: - 设计 - UML @@ -20,25 +21,25 @@ permalink: /pages/dd5922/ > **部署图(Deployment Diagram)用于对系统的物理结构建模**。部署图将显示系统中的软件组件和硬件组件之间的关系以及处理工作的物理分布。 -
+
### 节点 节点既可以是硬件元素,也可以是软件元素。它显示为一个立方体,如下图所示。 -
+
### 节点实例 图可以显示节点实例,实例与节点的区分是:实例的名称带下划线,冒号放在它的基本节点类型之前。实例在冒号之前可以有名称,也可以没有名称。下图显示了一个具名的计算机实例。 -
+
### 节点构造型 为节点提供了许多标准的构造型,分别命名为 «cdrom», «cd-rom», «computer», «disk array», «pc», «pc client», «pc server», «secure», «server», «storage», «unix server», «user pc»。 并在节点符号的右上角显示适当的图标。 -
+
### 工件 @@ -46,25 +47,25 @@ permalink: /pages/dd5922/ 工件表示为带有工件名称的矩形,并显示«artifact»关键字和文档符号。 -
+
### 关联 在部署图的上下文联系中,关联代表节点间的联系通道。下图显示了一个网络系统的部署图,描述了网络协议为构造型和关联终端的多重性, -
+
### 作为容器的节点 节点可以包含其他元素,如组件和工件。下图显示了一个嵌入式系统某个部分的部署图。描写了一个被主板节点包含的可执行工件。 -
+
## 组件图 > **组件图(Component Diagram)描绘了组成一个软件系统的模块和嵌入控件**。组件图比类图具有更高层次的抽象-通常运行时一个组件被一个或多个类(或对象)实现。它们象积木那样使得组件能最终构成系统的绝大部分。 -
+
上图演示了一些组件和它们的内部关系。装配连接器(Assembly connectors)“连接”由"Product"和"Customer"的提供接口到由 "Order"指定的需求接口。 一个依赖关系映射了客户相关的帐户信息到“Order”需要的 "Payment"需求接口。 @@ -74,19 +75,19 @@ permalink: /pages/dd5922/ 组件可表示为带关键字 «component»的矩形类元;也可用右上角有组件图标的矩形表示。 -
+
### 装配连接器 装配连接器在组件 “Component1”的需求接口和另一个组件 “Component2”的提供接口之间建立桥梁; 这个桥梁使得一个组件能提供另一个组件所需要的服务。 -
+
### 带端口组件 使用端口的组件图允许在它的环境指定一个服务和行为,同时这个服务和行为也是组件需要的。当端口进行双向操作的时候,它可以指定输入和输出。下图详述了用于在线服务的带端口组件,它有两个提供接口 “order entry”和 “tracking”,也有 “payment” 需求接口。 -
+
## 包图 @@ -94,13 +95,13 @@ permalink: /pages/dd5922/ 下面是一个包图的例子。 -
+
包中的元素共享相同的命名空间,因此,一个指定命名空间的元素必须有唯一的名称。 包可以用来代表物理或逻辑关系。选择把类包括在指定的包里,有助于在同一个包里赋予这些类相同继承层次。通常认为把通过复合相关联的类,以及与它们相协作的类放在同一个包里。 -
+
在 UML2.5 中,包用文件夹来表示,包中的元素共享同一个命名空间,并且必须是可识别的,因此要有唯一的名称或类型。包必须显示包名,在附属方框部分有选择的显示包内的元素。 @@ -114,7 +115,7 @@ permalink: /pages/dd5922/ 下面的图显示了类之间的聚合关系。弱聚合(浅色箭头)表现在类 "Account" 使用 "AddressBook",但是不必要包含它的一个实例。强聚合(图中的黑色箭头)表示了目标类包含源类,例如,"Contact" 和 "ContactGroup"值被包含在 "AddressBook"中。 -
+
### 类(Classes) @@ -126,39 +127,39 @@ permalink: /pages/dd5922/ 在下面图中,类的类名显示在最上面的分栏,它下面的分栏显示详细属性,如:"center" 属性显示初始化的值。最后面的分栏显示操作,如: setWidth,setLength 和 setPosition 以及他们的参数。 属性和操作名前的标注表示了该属性或操作的可见性: 如果使用 "+"号,这个属性或操作是公共的 ; "-" 号则代表这个属性或操作是私有的。 "#"号是这个属性或操作被定义为保护的," \~" 号代表包的可见性。 -
+
### 接口(Interfaces) 接口是实施者同意满足的行为规范,是一种约定。实现一个接口,类必需支持其要求的行为,使系统按照同样的方式,即公共的接口,处理不相关的元素。 -
+
接口有相似于类的外形风格,含有指定的操作,如下图所示。如果没有明确的详细操作,也可以画成一个圆环。当画成圆环的时候,到这个环形标柱的实现连接没有目标箭头。 -
+
### 表(Tables) 表尽管不是基本 UML 的一部分,仍然是“图型”能完成的实例用。在右上角画一个表的小图标来表示。表属性用“图型” «column»表示。 绝大多数表单有一个主键,是由一个或几个字段组成的一个唯一的字码组合加主键操作来访问表格,主键操作“图型”为«PK»。 一些表有一个或多个外键,使用一个或多个字段加一个外键操作,映射到相关表的主键上去,外键操作“图型”为«FK»。 -
+
### 关联(Associations) 关联表明两个模型元素之间有关系,通常用在一个类中被实现为一个实例变量。连接符可以包含两端的命名的角色,基数性,方向和约束。关联是元素之间普通的关系。如果多于两个元素,也可以使用菱形的关联关系。当从类图生成代码时,关联末端的对象将变成目标类中实例变量。见下图示例 "playsFor" 将变成"Player"类中的实例变量。 -
+
### 泛化(Generalizations) 泛化被用来说明继承关系。连接从特定类元到一般类元。泛化的含义是源类继承了目标类的特性。下图的图显示了一个父类泛化一个子类, 类“Circle”的一个实例将会有属性 “ x_position”,“ y_position” , “radius” 和 方法 “display()”。 注意:类 "Shape" 是抽象的,类名显示为斜体。 -
+
下图显示了与上图相同信息的视图。 -
+
### 聚合(Aggregations) @@ -168,13 +169,13 @@ permalink: /pages/dd5922/ 下面的图示:显示了弱聚合和强聚合的不同。“ address book” 由许多 “contacts” 和 “contact groups”组成。 “contact group” 是一个“contacts”的虚分组; “contact”可以被包含在不止一个 “ contact group”。 如果你删除一个“ address book”,所有的 “contacts” 和 “contact groups” 也将会被删除;如果你删除“ contact group”, 没有 “contacts”会被删除。 -
+
### 关联类(Association Classes) 关联类是一个允许关联连接有属性和操作的构造。下面的示例:显示了远不止简单连接两个类的连接,如给“employee”分配项目。“ employee”在项目中所起的作用是一个复杂的实体,既有自身的也有不属于“employee” 或 “project” 类的细节。 例如,“ employee”可以同时为几个项目工作,有不同的职务头衔和对应的安全权限。 -
+
### 依赖(Dependencies) @@ -188,13 +189,13 @@ permalink: /pages/dd5922/ 是源对象执行或实现目标,实现被用来表达模型的可跟踪性和完整性-业务模型或需求被一个或多个用例实现,用例则被类实现,类被组件实现,等等。这种实现贯穿于系统设计的映射需求和类等,直至抽象建模水平级。从而确保整个系统的一张宏图,它也反映系统的所有微小组成,以及约束和定义它的细节。实现关系用带虚线的实箭头表示。 -
+
### 嵌套(Nestings) 嵌套连接符用来表示源元素嵌套在目标元素中。下图显示“ inner class”的定义,尽管在 EA 中,更多地按照着他们在项目层次视图中的位置来显示这种关系。 -
+
## 复合结构图 @@ -202,63 +203,63 @@ permalink: /pages/dd5922/ 类元素已经在类图部分被详细地阐述,这部分用来说明类表现复合元素的方式,如:暴露接口,包含端口和部件。 -
+
### 部件 部件是代表一组(一个或多个)实例的元素,这组实例的拥有者是一类元实例,例如:如果一个图的实例有一组图形元素,则这些图形元素可以被表示为部件,并可以对他们之间的某种关系建模。注意:一个部件可以在它的父类被删除之前从父类中被去掉,这样部件就不会被同时删除了。 部件在类或组件内部显示为不加修饰的方框。 -
+
### 端口 端口是类型化的元素,代表一个包含类元实例的外部可视的部分。端口定义了类元和它的环境之间的交互。端口显示在包含它的部件,类或组合结构的边缘上。端口指定了类元提供的服务,以及类元要求环境提供的服务。 端口显示为所属类元边界指定的方框。 -
+
### 接口 接口与类相似,但是有一些限制,所有的接口操作都是公共和抽象的,不提供任何默认的实现。所有的接口属性都必须是常量。然而,当一个类从一个单独的超级类继承而来,它可以实现多个接口。 当一个接口在图中单列出来,它既可以显示为类元素的方框,带 «interface» 关键字和表明它是抽象的斜体名称,也可以显示为圆环。 -
+
注意:圆环标注不显示接口操作。当接口显示为类所有的接口,它们会被当作暴露接口引用。暴露接口可以定义为是提供的,还是需求的。提供接口确认包含它的类元提供指定接口元素定义的操作,可通过类和接口间实现的连接来定义。需求接口说明该类元能与其他类元进行通信,这些类元提供了指定接口元素所定义的操作。需求接口可通过在类和接口间建立依赖连接来定义。 提供接口显示为“带棒球体”,依附在类元边缘。需求接口显示为“带棒杯体”,也是依附在类元边缘。 -
+
### 委托 委托连接器用来定义组件外部端口和接口的内部工作方式。委托连接器表示为带有 «delegate» 关键字的箭头。它连接组件的外部约定,表现为它的端口,到组件部件行为的内部实现。 -
+
### 协作 协作定义了一系列共同协作的角色,它们集体展示一个指定的设计功能。协作图应仅仅显示完成指定任务或功能的角色与属性。隔离主要角色是用来简化结构和澄清行为,也用于重用。一个协作通常实现一个模式。 协作元素显示为椭圆。 -
+
### 角色绑定 角色绑定连接器是一条从连接协作到所要完成该任务类元的连线。它显示为虚线,并在类元端显示作用名。 -
+
### 表现 表现连接器用于连接协作到类元来表示此类元中使用了该协作。显示为带关键字 «represents»的虚线箭头。 -
+
发生 发生连接器用于连接协作到类元来表示此协作表现了(同原文)该类元;显示为带关键字«occurrence»的虚线箭头。 -
+
## 对象图 @@ -268,19 +269,19 @@ permalink: /pages/dd5922/ 下面的图显示了类元素和对象元素外观上的不同。注意:类元素包括三个部分,分别是名字栏,属性栏和操作栏;对象元素默认为没有分栏。名称显示也有不同:对象名称有下划线,并可能显示该对象实例化所用类元的名称。 -
+
### 运行状态 类元元素可以有任意数量的属性和操作。在对象实例中不会被显示出来。但可能定义对象的运行状态,显示特殊实例的属性设置值。 -
+
### 类和对象图示例 下图是一个对象图,其中插入了类定义图。它例示如何用对象图来测试类图中任务多重性的方法。“car” 类对 “wheel” 类有“1 对多” 的多重性,但是如果已经选择用“1 对 4” 来替代,那样就不会在对象图显示“3 个轮子”的汽车。 -
+
## 参考资料 diff --git "a/source/_posts/03.\350\256\276\350\256\241/11.UML/03.UML\350\241\214\344\270\272\345\273\272\346\250\241\345\233\276.md" "b/source/_posts/03.\350\256\276\350\256\241/05.UML/03.UML\350\241\214\344\270\272\345\273\272\346\250\241\345\233\276.md" similarity index 84% rename from "source/_posts/03.\350\256\276\350\256\241/11.UML/03.UML\350\241\214\344\270\272\345\273\272\346\250\241\345\233\276.md" rename to "source/_posts/03.\350\256\276\350\256\241/05.UML/03.UML\350\241\214\344\270\272\345\273\272\346\250\241\345\233\276.md" index 57138a1c2d..055cf7dba1 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/11.UML/03.UML\350\241\214\344\270\272\345\273\272\346\250\241\345\233\276.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/05.UML/03.UML\350\241\214\344\270\272\345\273\272\346\250\241\345\233\276.md" @@ -1,6 +1,7 @@ --- title: UML 行为建模图 date: 2018-10-17 13:25:36 +order: 03 categories: - 设计 - UML @@ -21,7 +22,7 @@ permalink: /pages/0b8e4b/ > UML 中,活动图用来展示活动的顺序。**显示了从起始点到终点的工作流,描述了活动图中存在于事件进程的判断路径**。活动图可以用来详细阐述某些活动执行中发生并行处理的情况。活动图对业务建模也比较有用,用来详细描述发生在业务活动中的过程。 > 一个活动图的示例如下所示。 -
+
下面描述组成活动图的元素。 @@ -29,41 +30,41 @@ permalink: /pages/0b8e4b/ 活动是行为参数化顺序的规范。活动被表示为圆角矩形,内含全部的动作,工作流和其他组成活动的元素。 -
+
### 动作 一个动作代表活动中的一个步骤。动作用圆角矩形表示。 -
+
### 动作约束 动作可以附带约束,下图显示了一个带前置条件和后置条件的动作。 -
+
### 控制流 控制流显示一个动作到下一个动作的流。表示为带箭头实线 -
+
### 初始节点 一个开始或起始点用大黑圆点表示,如下图。 -
+
### 结束节点 结束节点有两种类型:活动结束节点和流结束节点。活动结束节点表示为中心带黑点的圆环。 -
+
流结束节点表示为内部为叉号的圆环。 -
+
这两种不同类型节点的区别为:流结束节点表明单独的控制流的终点。活动结束终点是活动图内所有控制流的结束。 @@ -71,31 +72,31 @@ permalink: /pages/0b8e4b/ 对象流是对象和数据转递的通道。对象显示为矩形。 -
+
对象流显示为带箭头的连接器,表明方向和通过的对象。 -
+
一个对象流在它的至少一个终端有一个对象。在上图中,可以采用带输入输出引脚的速记标柱表示。 -
+
数据存储显示为带 «datastore» 关键字的对象。 -
+
### 判断节点和合并节点 判断节点和合并节点是相同标注:菱形。它们可以被命名。从判断节点出来的控制流有监护条件,当监护条件满足时,可以对流控制。下图显示了判断节点和合并节点的使用。 -
+
### 分叉和结合节点 分叉和结合节点有同样的标柱:垂直或水平条(方向取决于工作流从左到右,还是从上到下)。它们说明了控制的并发线程的起始和终点,下图显示他们的使用示例。 -
+
结合节点与合并节点不同之处在于:结合节点同步两个输入量,产生一个单独的输出量。来自结合节点的输出量要接收到所有的输入量后才能执行。合并节点直接将控制流传递通过。如果两个或更多的输入量到达合并节点。则它的输出流指定的动作会被执行两次或更多次。 @@ -103,25 +104,25 @@ permalink: /pages/0b8e4b/ 扩展域是会执行多次的结构活动域。输入输出扩展节点表示为一组“3 厢” ,代表多个选择项。关键词 "iterative", "parallel" 或 "stream"显示在区域的左上角 -
+
### 异常处理器 异常处理器在活动图中可以建模。 -
+
### 可中断活动区 可中断活动区环绕一组可以中断的动作。在下面非常简单的例子中: 当控制被传递到结束订单 "Close Order" 动作,定单处理"Process Order" 动作会执行直到完成,除非"Cancel Request"取消请求中断被接受,这会将控制传递给"Cancel Order"动作。 -
+
### 分割 一个活动分割显示为垂直或水平泳道。在下图中,分割被用来在活动图中分隔动作,有在 "accounting department"中执行的,有在 "customer"中执行的。 -
+
## 状态机图 @@ -129,7 +130,7 @@ permalink: /pages/0b8e4b/ 如下示例, 下列的状态机图显示了门在它的整个生命周期里如何运作。 -
+
门可以处于以下的三种状态之一: "Opened"打开状态, "Closed"关闭状态,或者"Locked"锁定状态。 它分别响应事件:“Open”开门, “Close”关门, “Lock”锁门 和 “Unlock”解锁。 注意:不是所有的事件,在所有的状态下都是有效的。如:一个门打开的时候是不可能锁定的,除非你关上门。并且,状态转移可能有附加监护条件:假设门是开的,如果“doorWay->isEmpty”(门是空的)被满足,那么它只能响应关门事件。状态机图使用的语法和约定将在下面的部分进行讨论。 @@ -137,19 +138,19 @@ permalink: /pages/0b8e4b/ 状态被表示为圆角矩形,状态名写在里面。 -
+
### 起始和结束状态 初始状态表示为实心黑圆环,可以标注名称。结束状态表示为中心带黑点圆环,也可以被标注名称。 -
+
### 转移 一个状态到下一个状态的转移表示为带箭头实线。转移可以有一个“Trigger”触发器,一个“Guard”监护条件和一个“effect”效果。如下所示: -
+
"Trigger"触发器是转移的起因,它可以是某个条件下的一个信号,一个事件,一个变化或一个时间通路。"Guard"监护是一个条件,而且必须为真,以便于让触发器引起转移。效果"Effect"是直接作用到对象上的一个动作,该对象具有做为转移结果的状态机。 @@ -157,7 +158,7 @@ permalink: /pages/0b8e4b/ 在上面的状态转移示例中,一个效果与该转移相关联。如果目标状态有多个转移到达,并且每一个转移都有相同的效果与它相关联,那最好将该效果与目标状态相关联,而不与转移相关联。你可以通过为这个状态定义初始动作来实现。下图显示了一个带入口动作和出口动作的状态。 -
+
可以定义发生在事件上的动作或一直发生的动作。每一种类型的动作是可以定义任意数量的。 @@ -165,17 +166,17 @@ permalink: /pages/0b8e4b/ 一个状态可能有一个返回到自身的转移,如下图。效果与转移关联是十分有帮助。 -
+
### 复合状态 一个状态机图可以有子状态机图,如下图所示: -
+
可选择不同方式显示相同信息,如下图所示: -
+
上面版本的标注说明"Check PIN"的子状态机图显示在单独的图中。 @@ -183,35 +184,35 @@ permalink: /pages/0b8e4b/ 有时,你不想在正常的初始状态进入子状态机。例如下面的子状态机,它通常从"初始化"状态开始,但是如果因为某些原因,它不必执行初始化,可能靠转移到指定的入口点来从 "Ready" 状态开始。 -
+
下图显示了状态机的上一层。 -
+
### 出口点 有与入口点相类似的方式,它可能也指定可选择的出口点。下图给出了主处理状态执行后,所执行状态的去向将取决于该状态转移时所使用的路径。 -
+
### 选择伪状态 选择伪状态显示为菱形,有一个转移输入,两个或多个输出。下图显示不管到达哪一个状态,经过选择伪状态后的去向,取决于在伪状态中执行时所选择的消息格式。 -
+
### 连接伪状态 连接伪状态用来将多个状态转移链接在一起。一个单独的连接伪状态可以有一个或多个输入和一个或多个输出,监护可能应用于每一个转移,连接是没有语义的。连接可以把一个输入转移分成多个输出转移来实现一个静态分支。与之对照的是选择伪状态实现一个动态条件分支。 -
+
### 终止伪状态 进入终止伪状态是指状态机生命线已经终止。终止伪状态表示为叉号。 -
+
### 历史状态 @@ -223,7 +224,7 @@ permalink: /pages/0b8e4b/ 一个状态可以被分成几个不同的区,包含同时存在和执行的子状态。下面的例子显示状态 "Applying Brakes", "front brake"和"rear brakes" 将同时独立运作。注意使用了分叉和结合伪状态而不是选择和合并伪状态。这些符号用来同步并发的线程。 -
+
## 用例图 @@ -233,25 +234,25 @@ permalink: /pages/0b8e4b/ 用例图显示了系统和系统外实体之间的交互。这些实体被引用为执行者。执行者代表角色,可以包括:用户,外部硬件和其他系统。执行者往往被画成简笔画小人。也可以用带«actor»关键字的类矩形表示。 -
+
在下图中,执行者可以详细的泛化其他执行者: -
+
### 用例 用例是有意义的单独工作单元。它向系统外部的人或事提供一个易于观察的高层次行为视图。 用例的标注符号是一个椭圆。 -
+
使用用例的符号是带可选择箭头的连接线,箭头显示控制的方向。下图说明执行者 "Customer"使用 "Withdraw"用例。 -
+
用途连接器(uses connector)可以有选择性的在每一个端点有多重性值,如下图,显示客户一次可能只执行一次取款交易。但是银行可以同时执行许多取款交易。 -
+
### 用例定义 @@ -266,25 +267,25 @@ permalink: /pages/0b8e4b/ ### 包含用例 -用例可能包含其他用例的功能来作为它正常处理的一部分。通常它假设,任何被包含的用例在基本程序运行时每一次都会被调用。下面例子:用例“卡的确认” 在运行时,被用例“取钱”当作一个子部分。 +用例可能包含其他用例的功能来作为它正常处理的一部分。通常它假设,任何被包含的用例在基本程序运行时每一次都会被调用。下面例子:用例“卡的确认”`` 在运行时,被用例“取钱”``当作一个子部分。 -
+
用例可以被一个或多个用例包含。通过提炼通用的行为,将它变成可以多次重复使用的用例。有助于降低功能重复级别。 ### 扩展用例 -一个用例可以被用来扩展另一个用例的行为,通常使用在特别情况下。例如:假设在修改一个特别类型的客户订单之前,用户必须得到某种更高级别的许可,然后“获得许可”用例将有选择的扩展常规的“修改订单”用例。 +一个用例可以被用来扩展另一个用例的行为,通常使用在特别情况下。例如:假设在修改一个特别类型的客户订单之前,用户必须得到某种更高级别的许可,然后“获得许可”``用例将有选择的扩展常规的“修改订单”``用例。 -
+
**扩展点** - 扩展用例的加入点被定义为扩展点。 -
+
**系统边界** - 它用来显示用例在系统内部,执行者在系统的外部。 -
+
## 通信图 @@ -294,9 +295,9 @@ permalink: /pages/0b8e4b/ 下面的两个图用通信图和时序图分别显示相同的信息。尽管我们可能从通信图的编号码得到消息顺序,但它不是立即可见的。通信图十分清楚的显示了邻近对象间全部完整的消息传递。 -
+
-
+
## 交互概述图 @@ -306,19 +307,19 @@ permalink: /pages/0b8e4b/ 交互发生引用现有的交互图。显示为一个引用框,左上角显示 "ref" 。被引用的图名显示在框的中央。 -
+
### 交互元素 交互元素与交互发生相似之处在于都是在一个矩形框中显示一个现有的交互图。不同之处在内部显示参考图的内容不同。 -
+
### 将它们放在一起 所有的活动图控件,都可以相同地被使用于交互概览图,如:分叉,结合,合并等等。它把控制逻辑放入较低一级的图中。下面的例子就说明了一个典型的销售过程。子过程是从交互发生抽象而来。 -
+
## 时序图 @@ -328,17 +329,17 @@ permalink: /pages/0b8e4b/ 一条生命线在时序图中代表一个独立的参与者。表示为包含对象名的矩形,如果它的名字是"self",则说明该生命线代表控制带时序图的类元。 -
+
有时,时序图会包含一个顶端是执行者的生命线。这情况说明掌握这个时序图的是用例。健壮图中的边界,控制和实体元素也可以有生命线。 -
+
### 消息 消息显示为箭头。消息可以完成传输,也可能丢失和找回,它可以是同步的,也可以是异步的,即可以是调用,也可以是信号。在下图中,第一条消息是同步消息(标为实箭头)完成传输,并隐含一条返回消息。第二条消息是异步消息 (标为实线箭头),第三条是异步返回消息(标为虚线)。 -
+
### 执行发生 @@ -348,25 +349,25 @@ permalink: /pages/0b8e4b/ 内部消息表现为一个操作的递归调用,或一个方法调用属于同一个对象的其他方法。显示为生命线上执行事件的嵌套控制焦点。 -
+
### 迷路消息和拾取消息 迷路消息是那些发送了却没有到达指定接收者,或者到达的接收者不再当前图中。拾取消息是收到来自那些未知的发送者,或者来自没有显示在当前图的发送者的消息。它们都表明是去往或来自一个终点元素。 -
+
### 生命线开始与结束 生命线可以在时序图时间刻度范围内创建和销毁,在下面的例子中,生命线被停止符号(叉号)终止。在前面的例子中,生命线顶端的符号(Child)显示在比创建它的对象符号(parent)沿页面要低的位置上。下图显示创建和终止对象。 -
+
### 时间和期限约束 消息默认显示为水平线。因为生命线显示为沿屏幕向下的时间通道,所以当给实时系统建模,或是有时间约束的业务过程建模,考虑执行动作所需时间长度是很重要的。因此可以给消息设置一个期限约束,这样的消息显示为下斜线。 -
+
### 复合片段 @@ -387,7 +388,7 @@ permalink: /pages/0b8e4b/ 下图显示的是循环片段: -
+
这也是一个类似于复合片段的交互发生。 交互发生被其他图参考,显示为左上角带"ref",将被参考图名显示在方框的中间。 @@ -395,21 +396,21 @@ permalink: /pages/0b8e4b/ 门是连接片段内消息和片段外消息的连接点。 在 EA 中,门显示为片段框架上的小正方形。作用为时序图与页面外的连接器。 用来表示进来的消息源,或者出去消息的终点。下面两个图显示它们在实践中的使用。注意:" top level diagram"中的门用消息箭头指向参考片段,在这里没有必要把它画成方块。 -
+
-
+
### 部分分解 一个对象可以引出多条生命线,使得对象内部和对象之间的消息显示在同一图上。 -
+
### 状态常量/延续 状态常量是生命线的约束,运行时始终为"真"。显示为两侧半圆的矩形,如下图: -
+
延续虽与状态常量有同样的标注,但是被用于复合片段,并可以延伸跨越多条生命线。 @@ -421,19 +422,19 @@ permalink: /pages/0b8e4b/ 状态生命线显示随时间变化,一个单项状态的改变。不论时间单位如何选择,X 轴显示经过的时间,Y 轴被标为给出状态的列表。状态生命线如下所示: -
+
### 值生命线 值生命线显示随时间变化,一个单项的值的变化。X 轴显示经过的时间,时间单位为任意,和状态生命线一样。平行线之间显示值,每次值变化,平行线交叉。如下图所示。 -
+
### 将它们放在一起 状态和值的生命线能叠加组合。它们必须有相同的 X 轴。 消息可以从一个生命线传递到另一个。每一个状态和值的变换能有一个定义的事件,一个时间限制是指一个事件何时必须发生,和一个期限限制说明状态或值多长时间必须有效。一旦这些已经被应用,其时间图可能显示如下。 -
+
## 参考资料 diff --git "a/source/_posts/03.\350\256\276\350\256\241/11.UML/README.md" "b/source/_posts/03.\350\256\276\350\256\241/05.UML/README.md" similarity index 94% rename from "source/_posts/03.\350\256\276\350\256\241/11.UML/README.md" rename to "source/_posts/03.\350\256\276\350\256\241/05.UML/README.md" index b5e2135938..4b260cffdf 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/11.UML/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/05.UML/README.md" @@ -9,6 +9,7 @@ tags: - UML permalink: /pages/13ccb0/ hidden: true +index: false --- # UML @@ -32,4 +33,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/03.\350\256\276\350\256\241/README.md" "b/source/_posts/03.\350\256\276\350\256\241/README.md" index c68f94887c..3bd1d954c3 100644 --- "a/source/_posts/03.\350\256\276\350\256\241/README.md" +++ "b/source/_posts/03.\350\256\276\350\256\241/README.md" @@ -7,6 +7,7 @@ tags: - 编程 permalink: /pages/8ea43c/ hidden: true +index: false --- # 设计 @@ -97,9 +98,9 @@ hidden: true ### UML -- [UML 快速入门](11.UML/01.UML快速入门.md) -- [UML 结构建模图](11.UML/02.UML结构建模图.md) -- [UML 行为建模图](11.UML/03.UML行为建模图.md) +- [UML 快速入门](05.UML/01.UML快速入门.md) +- [UML 结构建模图](05.UML/02.UML结构建模图.md) +- [UML 行为建模图](05.UML/03.UML行为建模图.md) ## 📚 资料 @@ -122,4 +123,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/04.DevOps/00.\347\273\274\345\220\210/01.DevOps\347\256\200\344\273\213.md" "b/source/_posts/04.DevOps/00.\347\273\274\345\220\210/01.DevOps\347\256\200\344\273\213.md" index d68eb629b7..1b40fcb87a 100644 --- "a/source/_posts/04.DevOps/00.\347\273\274\345\220\210/01.DevOps\347\256\200\344\273\213.md" +++ "b/source/_posts/04.DevOps/00.\347\273\274\345\220\210/01.DevOps\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: DevOps 简介 date: 2022-06-01 09:57:41 +order: 01 categories: - DevOps - 综合 @@ -35,7 +36,7 @@ DevOps 价值观也适用于开发以外的团队。如果 QA、安全团队也 DevOps 生命周期由六个阶段组成,分别代表开发和运维所需的流程、功能和工具。在每个阶段,团队协作和沟通以保持一致性、速度和质量。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220601155057.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220601155057.png) _图片来自 https://www.tasksgrid.com/devops-guide/_ diff --git "a/source/_posts/04.DevOps/03.\347\233\221\346\216\247/01.\347\233\221\346\216\247\344\275\223\347\263\273.md" "b/source/_posts/04.DevOps/03.\347\233\221\346\216\247/01.\347\233\221\346\216\247\344\275\223\347\263\273.md" index 4b144ecf1c..5d784e66fa 100644 --- "a/source/_posts/04.DevOps/03.\347\233\221\346\216\247/01.\347\233\221\346\216\247\344\275\223\347\263\273.md" +++ "b/source/_posts/04.DevOps/03.\347\233\221\346\216\247/01.\347\233\221\346\216\247\344\275\223\347\263\273.md" @@ -1,6 +1,7 @@ --- title: 如何建设监控体系 date: 2022-04-19 20:02:48 +order: 01 categories: - DevOps - 监控 @@ -41,7 +42,7 @@ permalink: /pages/e593a4/ 完整的监控流程主要包括以下环节:采集、传输、存储、分析、展示、告警、处理。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220602172630.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220602172630.png) ### 数据采集 diff --git "a/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" "b/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" index 191ae58c9f..ac615f1f4e 100644 --- "a/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" +++ "b/source/_posts/04.DevOps/03.\347\233\221\346\216\247/02.\351\223\276\350\267\257\350\277\275\350\270\252.md" @@ -1,6 +1,7 @@ --- title: 链路追踪 date: 2022-04-20 09:08:29 +order: 02 categories: - DevOps - 监控 @@ -20,7 +21,7 @@ permalink: /pages/b46249/ 链路追踪系统**广义**的概念是:由**数据采集**、**数据处理**和**数据展示**三个相对独立的模块所构成的分布式追踪系统;链路追踪系统**狭义**的概念是:特指链路追踪的数据采集。譬如 [Spring Cloud Sleuth](https://spring.io/projects/spring-cloud-sleuth) 就属于狭义的链路追踪系统,通常会搭配 [Zipkin](https://github.com/openzipkin/zipkin) 作为数据展示,搭配 Elasticsearch 作为数据存储来组合使用;而 [Zipkin](https://github.com/openzipkin/zipkin)、[Pinpoint](https://github.com/pinpoint-apm/pinpoint)、[SkyWalking](https://github.com/apache/skywalking)、[CAT](https://github.com/dianping/cat) 都属于广义的链路追踪系统。 -个人理解,链路追踪的**本质**就是,通过一个全局唯一的 ID,将分布在各个服务节点上的同一次请求产生的数据串联起来,从而梳理出调用关系,进而辅助分析系统问题、分析调用数据并统计各种系统指标。 +个人理解,链路追踪的**本质**就是,通过全局唯一的 ID,将分布在各个服务节点上的同一次请求产生的数据串联起来,从而梳理出调用关系,进而辅助分析系统问题、分析调用数据并统计各种系统指标。 ### 为什么需要链路追踪 @@ -39,12 +40,12 @@ Google 发布的一篇的论文 [`Dapper, a Large-Scale Distributed Systems Trac Dapper 提出了一些很重要的核心概念:Trace、Span、Annonation 等,这是理解链路追踪原理的前提。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230413161335.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230413161335.png) _Trace 和 Spans(图片来源于[Dapper 论文](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/papers/dapper-2010-1.pdf))_ - **Trace (追踪)** - 代表一次完整的请求。一次完整的请求是指,从客户端发起请求,记录请求流转的每一个服务,直到客户端收到响应为止。整个过程中,当请求分发到第一层级的服务时,就会生成一个全局唯一的 **Trace ID**,并且会随着请求分发到每一层级。因此,通过 **Trace ID** 就可以把一次用户请求在系统中调用的链路串联起来。 -- **Span (跨度)** - 链路追踪的基本单元。由于每次 Trace 都可能会调用数量不定、坐标不定的多个服务,为了能够记录具体调用了哪些服务,以及调用的顺序、开始时点、执行时长等信息,每次开始调用服务前都要先埋入一个调用记录,这个记录称为一个 Span。 +- **Span (跨度)** - 代表一次调用,也是链路追踪的基本单元。由于每次 Trace 都可能会调用数量不定、坐标不定的多个服务,为了能够记录具体调用了哪些服务,以及调用的顺序、开始时点、执行时长等信息,每次开始调用服务前都要先埋入一个调用记录,这个记录称为一个 Span。 - Span 的数据结构应该足够简单,以便于能放在日志或者网络协议的报文头里;也应该足够完备,起码应含有时间戳、起止时间、Trace 的 ID、当前 Span 的 ID、父 Span 的 ID 等能够满足追踪需要的信息。 - Trace 实际上都是由若干个有顺序、有层级关系的 Span 所组成一颗 Trace Tree (追踪树)。 - **Annotation**:用于业务自定义埋点数据,例如:一次请求的用户 ID,某一个支付订单的订单 ID 等。 @@ -60,7 +61,7 @@ _Trace 和 Spans(图片来源于[Dapper 论文](https://static.googleuserconte 下图显示了 Span 和 Trace 在系统中的样子。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220420092134.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220420092134.png) (图片来源于 [spring-cloud-sleuth 文档](https://docs.spring.io/spring-cloud-sleuth/docs/current/reference/html/getting-started.html#getting-started-terminology)) @@ -78,7 +79,7 @@ _Trace 和 Spans(图片来源于[Dapper 论文](https://static.googleuserconte 下图显示了 span 的父子关系: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230414173703.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230414173703.png) (图片来源于 [spring-cloud-sleuth 文档](https://docs.spring.io/spring-cloud-sleuth/docs/current/reference/html/getting-started.html#getting-started-terminology)) @@ -161,7 +162,7 @@ Adding a class tag with value [ProductController] to a span [Trace: cbe97e67ce16 下面是 Zipkin 的调用链路图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220420103316.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220420103316.png) #### 调用拓扑图 @@ -169,7 +170,7 @@ Adding a class tag with value [ProductController] to a span [Trace: cbe97e67ce16 下面是 Pinpoint 的调用链路图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220420103528.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220420103528.png) ## 链路追踪主流技术 @@ -190,4 +191,4 @@ Adding a class tag with value [ProductController] to a span [Trace: cbe97e67ce16 - **文章** - [Dapper 论文](https://research.google/pubs/pub36356/) - 即:Dapper, a Large-Scale Distributed Systems Tracing Infrastructure - [Dapper 论文翻译](http://bigbully.github.io/Dapper-translation/) - - [凤凰架构-链路追踪](http://icyfenix.cn/distribution/observability/tracing.html) + - [凤凰架构-链路追踪](http://icyfenix.cn/distribution/observability/tracing.html) \ No newline at end of file diff --git "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/01.\345\246\202\344\275\225\344\274\230\351\233\205\347\232\204\347\216\251\350\275\254Git.md" "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/01.\345\246\202\344\275\225\344\274\230\351\233\205\347\232\204\347\216\251\350\275\254Git.md" index 39ce11f532..ebb09c4eff 100644 --- "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/01.\345\246\202\344\275\225\344\274\230\351\233\205\347\232\204\347\216\251\350\275\254Git.md" +++ "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/01.\345\246\202\344\275\225\344\274\230\351\233\205\347\232\204\347\216\251\350\275\254Git.md" @@ -1,6 +1,7 @@ --- title: 如何优雅的玩转 Git date: 2019-03-09 00:25:13 +order: 01 categories: - DevOps - 工具 @@ -58,7 +59,7 @@ Git 是分布式的。这是 Git 和其它非分布式的版本控制系统( 当你一个项目到本地或创建一个 git 项目,项目目录下会有一个隐藏的 `.git` 子目录。这个目录是 git 用来跟踪管理版本库的,如果不熟悉其工作机制,千万不要手动修改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210419093855.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210419093855.png) - `hooks` 目录:包含客户端或服务端的钩子脚本(hook scripts) - `info` 目录:包含一个全局性排除(global exclude)文件, 用以放置那些不希望被记录在 `.gitignore` 文件中的忽略模式(ignored patterns)。 @@ -98,7 +99,7 @@ Git 中使用这种哈希值的情况很多,你将经常看到这种哈希值 - **本地仓库(local)** - 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 本地仓库。 - **远程仓库(remote)** - 以上几个工作区都是在本地。为了让别人可以看到你的修改,你需要将你的更新推送到远程仓库。同理,如果你想同步别人的修改,你需要从远程仓库拉取更新。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/web/git/git-theory.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/web/git/git-theory.png) ## 分支管理 @@ -106,7 +107,7 @@ Git 中使用这种哈希值的情况很多,你将经常看到这种哈希值 Git Flow 应该是目前流传最广的 Git 分支管理策略。Git Flow 围绕的核心点是版本发布(release),它适用于迭代版本较长的项目。 -> ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210419110634.png) +> ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210419110634.png) > 详细内容,可以参考这篇文章:[Git 在团队中的最佳实践--如何正确使用 Git Flow](http://www.cnblogs.com/cnblogsfans/p/5075073.html) @@ -122,7 +123,7 @@ Git Flow 工作流程 #### 2.1. 主干分支 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210419113532.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210419113532.png) 主干分支有两个,它们是伴随着项目生命周期长期存在的分支。 @@ -133,7 +134,7 @@ Git Flow 工作流程 这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回 develop 分支进入下一个 release。feature 分支开发结束后,必须合并回 develop 分支, 合并完分支后一般会删点这个 feature 分支,但是我们也可以保留。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210419114042.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210419114042.png) #### 2.3. **`release` 分支** @@ -165,7 +166,7 @@ git 提供了 `git flow` 命令来手动管理,但是比较麻烦,所以还 在 Github Flow 策略中,所有分支都是基于 master 创建。在 Feature 或 Bugfix 分支中完成工作后,将其合入 master,然后继续迭代。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210420194518.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210420194518.png) > 想了解更详细的 Github Flow 介绍,可以参考:[GitHub Flow](http://scottchacon.com/2011/08/31/github-flow.html) @@ -177,11 +178,11 @@ Git 每次提交代码,都要写 Commit message(提交说明),否则就 先来看下图中不好的 Commit message 范例,从提交信息完全看不出来修改了什么。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210420152215.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210420152215.png) 再来一张较好的 Commit message 范例,每次提交的是什么内容,做了什么一目了然。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210420151352.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210420151352.png) ### Commit message 的作用 @@ -228,11 +229,11 @@ Intellij 中有集成 [Angular Git Commit 规范](https://docs.google.com/docume 第一步,安装插件 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210419145223.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210419145223.png) 第二步,提交代码时,按照模板填写 commit message -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210419145327.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210419145327.png) ### 生成 Change log @@ -468,7 +469,7 @@ hs_err_pid* 示例,下面是携程 [apollo](https://github.com/ctripcorp/apollo) 的一个 Issue 模板,要求提问者填充 bug 描述、复现步骤、期望、截图、日志等细节。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210420114644.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210420114644.png) > 更多模板:[Github issue_templates 模板](https://github.com/stevemao/github-issue-templates) @@ -480,7 +481,7 @@ hs_err_pid* (2)在 `.gitlab` 目录中添加 `issue_templates` 目录,在其中添加的 md 文件都会被 Gitlab 自动识,并将其作为 issue 的默认模板。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210420141838.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210420141838.png) > 更多模板:[Gitlab 官方 issue_templates 模板](https://gitlab.com/gitlab-org/gitlab/-/tree/master/.gitlab/issue_templates) diff --git "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/02.Git\345\270\256\345\212\251\346\211\213\345\206\214.md" "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/02.Git\345\270\256\345\212\251\346\211\213\345\206\214.md" index ffef938700..5b407e753f 100644 --- "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/02.Git\345\270\256\345\212\251\346\211\213\345\206\214.md" +++ "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/02.Git\345\270\256\345\212\251\346\211\213\345\206\214.md" @@ -1,6 +1,7 @@ --- title: Git帮助手册 date: 2021-04-16 18:19:18 +order: 02 categories: - DevOps - 工具 @@ -17,7 +18,7 @@ permalink: /pages/09397d/ 本节选择性介绍 git 中比较常用的命令行场景。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/web/git/git-cheat-sheet.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/web/git/git-cheat-sheet.png) ## 安装 diff --git "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/README.md" "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/README.md" index 82d5638c5e..a9d7f3dd1d 100644 --- "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/README.md" +++ "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/01.Git/README.md" @@ -10,11 +10,12 @@ tags: - Git permalink: /pages/d107ad/ hidden: true +index: false --- # Git 教程 -![git脑图](https://raw.githubusercontent.com/dunwu/images/dev/cs/web/git/git-summary.png) +![git脑图](https://raw.githubusercontent.com/dunwu/images/master/cs/web/git/git-summary.png) ## 📖 内容 @@ -65,4 +66,4 @@ hidden: true ## 🚪 传送 -◾ 🏠 [dunwu.github.io 首页](https://dunwu.github.io/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 🏠 [dunwu.github.io 首页](https://dunwu.github.io/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" index 28647ddf9f..a0caae6299 100644 --- "a/source/_posts/04.DevOps/99.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" +++ "b/source/_posts/04.DevOps/99.\345\267\245\345\205\267/99.\345\205\266\344\273\226/01.\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" @@ -1,6 +1,7 @@ --- title: 正则表达式极简教程 date: 2016-10-10 11:45:26 +order: 01 categories: - DevOps - 工具 @@ -247,15 +248,15 @@ long not matches: [a-z]{0,}ing$ 下表中的等价字符都表示某一类型的字符。 -| 字符 | 描述 | -| -------- | ------------------------------------------------------------------------------------------------- | -| **`.`** | 匹配除“\n”之外的任何单个字符。 | -| **`\d`** | 匹配一个数字字符。等价于[0-9]。 | -| **`\D`** | 匹配一个非数字字符。等价于[^0-9]。 | -| **`\w`** | 匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的单词字符指的是 Unicode 字符集。 | -| **`\W`** | 匹配任何非单词字符。 | -| **`\s`** | 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 | -| **`\S`** | 匹配任何可见字符。等价于[ \f\n\r\t\v]。 | +| 字符 | 描述 | +| -------- | ------------------------------------------------------------ | +| **`.`** | 匹配除 `\n` 之外的任何单个字符。 | +| **`\d`** | 匹配一个数字字符。等价于[0-9]。 | +| **`\D`** | 匹配一个非数字字符。等价于[^0-9]。 | +| **`\w`** | 匹配包括下划线的任何单词字符。类似但不等价于`[A-Za-z0-9_]`,这里的单词字符指的是 Unicode 字符集。 | +| **`\W`** | 匹配任何非单词字符。 | +| **`\s`** | 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于`[ \f\n\r\t\v]`。 | +| **`\S`** | 匹配任何可见字符。等价于`[ \f\n\r\t\v]`。 | **案例 基本等价字符的用法** @@ -358,13 +359,13 @@ apppppppppp not matches: ap? 下表从最高到最低说明了各种正则表达式运算符的优先级顺序: -| 运算符 | 说明 | -| ------------------------------- | ------------ | -| \\ | 转义符 | -| (), (?:), (?=), [] | 括号和中括号 | -| \*, +, ?, {n}, {n,}, {n,m} | 限定符 | -| ^, \$, \*任何元字符、任何字符\* | 定位点和序列 | -| \| | 替换 | +| 运算符 | 说明 | +| ---------------------------------- | ------------ | +| `\\` | 转义符 | +| `(), (?:), (?=), []` | 括号和中括号 | +| `\*, +, ?, {n}, {n,}, {n,m}` | 限定符 | +| `^`, `$`, `*` 任何元字符、任何字符 | 定位点和序列 | +| `|` | 替换 | 字符具有高于替换运算符的优先级,使得“m|food”匹配“m”或“food”。若要匹配“mood”或“food”,请使用括号创建子表达式,从而产生“(m|f)ood”。 @@ -443,11 +444,11 @@ regex = (?\w+)\s\k\W(?\w+), content: He **说明** -(?\w+): 匹配一个或多个单词字符。 命名此捕获组 duplicateWord。 -\s: 与空白字符匹配。 -\k: 匹配名为 duplicateWord 的捕获的组。 -\W: 匹配包括空格和标点符号的一个非单词字符。 这样可以防止正则表达式模式匹配从第一个捕获组的单词开头的单词。 -(?\w+): 匹配一个或多个单词字符。 命名此捕获组 nextWord。 +`(?\w+)`: 匹配一个或多个单词字符。 命名此捕获组 duplicateWord。 +`\s`: 与空白字符匹配。 +`\k`: 匹配名为 duplicateWord 的捕获的组。 +`\W`: 匹配包括空格和标点符号的一个非单词字符。 这样可以防止正则表达式模式匹配从第一个捕获组的单词开头的单词。 +`(?\w+)`: 匹配一个或多个单词字符。 命名此捕获组 nextWord。 ## 非捕获组 @@ -505,11 +506,11 @@ regex = \b\w+(?=\sis\b), content: Sunday is a weekend day. **说明** -\b: 在单词边界处开始匹配。 +`\b`: 在单词边界处开始匹配。 -\w+: 匹配一个或多个单词字符。 +`\w+`: 匹配一个或多个单词字符。 -(?=\sis\b): 确定单词字符是否后接空白字符和字符串“is”,其在单词边界处结束。 如果如此,则匹配成功。 +`(?=\sis\b)`: 确定单词字符是否后接空白字符和字符串“is”,其在单词边界处结束。 如果如此,则匹配成功。 ### 匹配 exp 后面的位置 @@ -559,13 +560,13 @@ regex = \b(?!un)\w+\b, content: unite one unethical ethics use untie ultimate **说明** -\b: 在单词边界处开始匹配。 +`\b`: 在单词边界处开始匹配。 -(?!un): 确定接下来的两个的字符是否为“un”。 如果没有,则可能匹配。 +`(?!un)`: 确定接下来的两个的字符是否为“un”。 如果没有,则可能匹配。 -\w+: 匹配一个或多个单词字符。 +`\w+`: 匹配一个或多个单词字符。 -\b: 在单词边界处结束匹配。 +`\b`: 在单词边界处结束匹配。 ### 匹配前面不是 exp 的位置 diff --git a/source/_posts/04.DevOps/README.md b/source/_posts/04.DevOps/README.md index ff63904c2f..404265574d 100644 --- a/source/_posts/04.DevOps/README.md +++ b/source/_posts/04.DevOps/README.md @@ -7,6 +7,7 @@ tags: - DevOps permalink: /pages/1883b8/ hidden: true +index: false --- # DevOps @@ -34,4 +35,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" index 73b2f0ce02..40802bb740 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/01.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225\346\214\207\345\215\227.md" @@ -1,6 +1,7 @@ --- title: 数据结构和算法指南 date: 2015-03-10 18:29:37 +order: 01 categories: - 数据结构和算法 - 综合 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" index 1f1a4b1a05..de87d309a5 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/00.\347\273\274\345\220\210/02.\345\244\215\346\235\202\345\272\246\345\210\206\346\236\220.md" @@ -1,6 +1,7 @@ --- title: 复杂度分析 date: 2022-03-20 23:25:17 +order: 02 categories: - 数据结构和算法 - 综合 @@ -85,7 +86,7 @@ T(n) = (M-1)(N-1) = O(M*N) ≈ O(N^2) 【示例】递归函数的时间复杂度是多少?思考一下斐波那契数列 `f(n) = f(n-1) + f(n-2)` 的时间复杂度是多少? -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320110642.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320110642.png) ``` T(n) = O(2^N) @@ -113,7 +114,7 @@ T(n) = O(2^N) 在数据量比较小的时候,复杂度量级差异并不明显;但是,随着数据规模大小的变化,差异会逐渐突出。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320160627.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320160627.png) `O(1)` 复杂度示例: @@ -163,7 +164,7 @@ for (int i = 1; i <= Math.pow(2, max); i++) { ## 常见数据结构的复杂度 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200702071922.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) ## 参考资料 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" index d689e78767..9d1d22889b 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/01.\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250.md" @@ -1,6 +1,7 @@ --- title: 数组和链表 date: 2015-04-10 18:46:13 +order: 01 categories: - 数据结构和算法 - 线性表 @@ -24,17 +25,17 @@ permalink: /pages/6c31ed/ 数组元素的访问是以行或列索引的单一下标表示。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320115836.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115836.png) 在上面的例子中,数组 a 中有 5 个元素。`也就是说`,a 的长度是 6 。我们可以使用 a[0] 来表示数组中的第一个元素。因此,a[0] = A 。类似地,a[1] = B,a[2] = C,依此类推。 ### 数组的插入 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320115848.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115848.png) ### 数组的删除 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320115859.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115859.png) ### 数组的特性 @@ -50,17 +51,17 @@ permalink: /pages/6c31ed/ 数组是有下标和值组成集合。 -如果数组的下标有多个维度,即为多维数组。比如:二维数组可以视为『数组元素为一维数组』的一维数组;三维数组可以视为『数组元素为二维数组』的一维数组;依次类推。 +如果数组的下标有多个维度,即为多维数组。比如:二维数组可以视为“数组元素为一维数组”的一维数组;三维数组可以视为“数组元素为二维数组”的一维数组;依次类推。 下图是由 M 个行向量,N 个列向量组成的二维数组. -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320152607.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320152607.png) ## 链表 > **链表用不连续的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链**。 -区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为「结点」复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,链表在插入数据的时候可以达到 `O(1)` 的复杂度,但是查找一个节点或者访问特定编号的节点则需要 `O(n)` 的时间。 +区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为“结点”复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,链表在插入数据的时候可以达到 `O(1)` 的复杂度,但是查找一个节点或者访问特定编号的节点则需要 `O(n)` 的时间。 链表具有以下特性: @@ -79,7 +80,7 @@ permalink: /pages/6c31ed/ 单链表中的每个结点不仅包含数据值,还包含一个指针,指向其后继节点。通过这种方式,单链表将所有结点按顺序组织起来。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320174829.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174829.png) 与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按 `索引` 来 `访问元素` 平均要花费 `O(N)` 时间,其中 N 是链表的长度。 @@ -89,15 +90,15 @@ permalink: /pages/6c31ed/ (1)使用给定值初始化新结点 `cur`; -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320174908.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174908.png) (2)将 `cur` 的 `next` 字段链接到 `prev` 的下一个结点 `next` ; -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320174919.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174919.png) (3)将 `prev` 中的 `next` 字段链接到 `cur` 。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320174932.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174932.png) 与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在 `O(1)` 时间复杂度中将新结点插入到链表中,这非常高效。 @@ -107,11 +108,11 @@ permalink: /pages/6c31ed/ (1)找到 `cur` 的上一个结点 `prev` 及其下一个结点 `next` ; -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320174953.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174953.png) (2)接下来链接 `prev` 到 `cur` 的下一个节点 `next` 。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320175006.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320175006.png) 在我们的第一步中,我们需要找出 `prev` 和 `next`。使用 `cur` 的参考字段很容易找出 `next`,但是,我们必须从头结点遍历链表,以找出 `prev`,它的平均时间是 `O(N)`,其中 `N` 是链表的长度。因此,删除结点的时间复杂度将是 `O(N)`。 @@ -123,7 +124,7 @@ permalink: /pages/6c31ed/ 单链表的访问是单向的,而双链表的访问是双向的。显然,双链表比单链表操作更灵活,但是空间开销也更大。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320181150.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181150.png) 双链表以类似的方式工作,但`还有一个引用字段`,称为`“prev”`字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。 @@ -133,15 +134,15 @@ permalink: /pages/6c31ed/ (1)使用给定值初始化新结点 `cur`; -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320181208.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181208.png) (2)链接 `cur` 与 `prev` 和 `next`,其中 `next` 是 `prev` 原始的下一个节点; -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320181303.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181303.png) (3)用 `cur` 重新链接 `prev` 和 `next`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320181504.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320181504.png) 与单链表类似,添加操作的时间和空间复杂度都是 `O(1)`。 @@ -162,11 +163,11 @@ permalink: /pages/6c31ed/ - 单链表的最后一个结点的后继指针 `next` 指向空地址。 - 循环链表的最后一个结点的后继指针 `next` 指向第一个节点(如果有头节点,就指向头节点)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220322190534.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322190534.png) #### 循环双链表 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220322190423.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322190423.png) ## 数组 vs. 链表 @@ -436,4 +437,4 @@ public DListNode find(E value) { - [数据结构(C 语言版)](https://item.jd.com/12407475.html) - [数据结构(C++语言版)](https://book.douban.com/subject/25859528/) - [Leetcode:数组和字符串](https://leetcode-cn.com/leetbook/detail/array-and-string/) -- [Leetcode:链表](https://leetcode-cn.com/tag/linked-list/) \ No newline at end of file +- [Leetcode:链表](https://leetcode-cn.com/tag/linked-list/) diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" index d40e533a15..ee3ec7b672 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/02.\346\240\210\345\222\214\351\230\237\345\210\227.md" @@ -1,6 +1,7 @@ --- title: 栈和队列 date: 2014-01-25 16:46:13 +order: 02 categories: - 数据结构和算法 - 线性表 @@ -24,7 +25,7 @@ permalink: /pages/dd3588/ **栈是一个 LIFO(后进先出) 数据结构**。**栈是一种“操作受限”的线性表**,只允许在一端插入和删除数据。通常,插入操作在栈中被称作入栈 push 。与队列类似,总是在堆栈的末尾添加一个新元素。但是,删除操作,退栈 pop ,将始终删除队列中相对于它的最后一个元素。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320200148.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320200148.png) **当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构**。 @@ -42,11 +43,11 @@ permalink: /pages/dd3588/ (1)**函数调用栈** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310091000.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310091000.jpg) (2)**表达式求值** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310091100.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310091100.jpg) (3)**表达式匹配** @@ -66,7 +67,7 @@ permalink: /pages/dd3588/ 队列的最基本操作:**入队 `enqueue()`**,放一个数据到队列尾部;**出队 `dequeue()`**,从队列头部取一个元素。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320200213.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320200213.png) 队列可以用数组来实现,也可以用链表来实现。用数组实现的队列叫作**顺序队列**,用链表实现的队列叫作**链式队列**。 @@ -80,7 +81,7 @@ permalink: /pages/dd3588/ 在用数组实现的非循环队列中,队满的判断条件是 `(tail+1) % n == head`,队空的判断条件是 `head == tail`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220322214822.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220322214822.png) ### 为什么需要队列 @@ -95,9 +96,9 @@ permalink: /pages/dd3588/ - 在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回; - 如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310092908.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310092908.jpg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310093026.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310093026.jpg) (2)**并发队列** diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" index 8f0394968d..91aecffee1 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/11.\347\272\277\346\200\247\350\241\250\347\232\204\346\237\245\346\211\276.md" @@ -1,6 +1,7 @@ --- title: 线性表的查找 date: 2015-03-10 18:29:13 +order: 11 categories: - 数据结构和算法 - 线性表 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" index 9f46ff5101..493ded5741 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/01.\347\272\277\346\200\247\350\241\250/12.\347\272\277\346\200\247\350\241\250\347\232\204\346\216\222\345\272\217.md" @@ -1,6 +1,7 @@ --- title: 线性表的排序 date: 2015-03-03 17:37:24 +order: 12 categories: - 数据结构和算法 - 线性表 @@ -13,9 +14,9 @@ permalink: /pages/3bac06/ # 线性表的排序 -> 📦 本文已归档到:「[blog](https://github.com/dunwu/blog/tree/master/source/_posts/algorithm)」 +> 📦 本文已归档到:“[blog](https://github.com/dunwu/blog/tree/master/source/_posts/algorithm)” > -> 🔁 本文中的示例代码已归档到:「[algorithm-tutorial](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)」 +> 🔁 本文中的示例代码已归档到:“[algorithm-tutorial](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java)” ## 冒泡排序 @@ -35,7 +36,7 @@ permalink: /pages/3bac06/ 假设有一个大小为 N 的无序序列。冒泡排序就是要每趟排序过程中通过两两比较,找到第 i 个小(大)的元素,将其往上排。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/bubble-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/bubble-sort.png) 以上图为例,演示一下冒泡排序的实际流程: @@ -178,7 +179,7 @@ public void bubbleSort_2(int[] list) { 详细的图解往往比大堆的文字更有说明力,所以直接上图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/quick-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/quick-sort.png) 上图中,演示了快速排序的处理过程: @@ -283,7 +284,7 @@ private void quickSort(int[] list, int left, int right) { 在讲解直接插入排序之前,先让我们脑补一下我们打牌的过程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/insert-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/insert-sort.png) - 先拿一张 5 在手里, - 再摸到一张 4,比 5 小,插到 5 前面, @@ -383,7 +384,7 @@ public void insertSort(int[] list) { 我们来通过演示图,更深入的理解一下这个过程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/shell-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/shell-sort.png) 在上面这幅图中: @@ -489,7 +490,7 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 **核心代码** -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/selection-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/selection-sort.png) ### 算法分析 @@ -543,7 +544,7 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 其中 i=1,2,…,n/2 向下取整; -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/heap-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort.png) 如上图所示,序列 R{3, 8,15, 31, 25} 是一个典型的小根堆。 @@ -577,13 +578,13 @@ Donald Shell 最初建议步长选择为 N/2 并且对步长取半直到步长 设有一个无序序列 { 1, 3,4, 5, 2, 6, 9, 7, 8, 0 }。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/heap-sort-02.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort-02.png) 构造了初始堆后,我们来看一下完整的堆排序处理: 还是针对前面提到的无序序列 { 1,3, 4, 5, 2, 6, 9, 7, 8, 0 } 来加以说明。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/heap-sort-03.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/heap-sort-03.png) 相信,通过以上两幅图,应该能很直观的演示堆排序的操作处理。 @@ -752,7 +753,7 @@ public void Merge(int[] array2, int low, int mid, int high) { 掌握了合并的方法,接下来,让我们来了解**如何分解**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/merge-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/merge-sort.png) 在某趟归并中,设各子表的长度为 **gap**,则归并前 R[0...n-1] 中共有 **n/gap** 个有序的子表:`R[0...gap-1]`, `R[gap...2*gap-1]`, ... , `R[(n/gap)*gap ... n-1]`。 @@ -846,7 +847,7 @@ public int[] sort(int[] list) { 我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是 0,将这个数存入编号为 0 的桶中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/algorithm/sort/radix-sort.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/algorithm/sort/radix-sort.png) 分类后,我们在从各个桶中,将这些数按照从编号 0 到编号 9 的顺序依次将所有数取出来。 @@ -889,4 +890,4 @@ public int[] sort(int[] list) { [我的 Github 测试例](https://github.com/dunwu/algorithm-tutorial/blob/master/codes/algorithm/src/test/java/io/github/dunwu/algorithm/sort/SortStrategyTest.java) -样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 \ No newline at end of file +样本包含:数组个数为奇数、偶数的情况;元素重复或不重复的情况。且样本均为随机样本,实测有效。 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" index b79dd5c7c8..251f18a490 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/01.\346\240\221\345\222\214\344\272\214\345\217\211\346\240\221.md" @@ -1,6 +1,7 @@ --- title: 树和二叉树 date: 2014-06-15 15:39:23 +order: 01 categories: - 数据结构和算法 - 树 @@ -28,7 +29,7 @@ permalink: /pages/133326/ - 每个非根节点可以分为多个不相交的子树。 - 树里面没有环路。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220403163620.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403163620.png) ### 树的术语 @@ -44,7 +45,7 @@ permalink: /pages/133326/ - **子孙**:以某节点为根的子树中任一节点都称为该节点的子孙。 - **森林**:由 m(m>=0)棵互不相交的树的集合称为森林; -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220403164732.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403164732.png) - **节点的高度**:节点到叶子节点的最长路径。高度是从下往上度量。 - **节点的深度**:根节点到该节点的最长路径。深度是从上往下度量。 @@ -87,7 +88,7 @@ permalink: /pages/133326/ 除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树就叫作**满二叉树**。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220403183927.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403183927.png) ### 完全二叉树 @@ -95,7 +96,7 @@ permalink: /pages/133326/ 特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220403183640.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403183640.png) 存储一棵二叉树,有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。 @@ -103,11 +104,11 @@ permalink: /pages/133326/ 每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220403212249.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403212249.png) **顺序存储法** -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220403214627.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220403214627.png) 如果节点 X 存储在数组中下标为 i 的位置,下标为 2 _ i 的位置存储的就是左子节点,下标为 2 _ i + 1 的位置存储的就是右子节点。反过来,下标为 i/2 的位置存储就是它的父节点。通过这种方式,我们只要知道根节点存储的位置(一般情况下,为了方便计算子节点,根节点会存储在下标为 1 的位置),这样就可以通过下标计算,把整棵树都串起来。 @@ -121,7 +122,7 @@ permalink: /pages/133326/ - **中序遍历**:对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。 - **后序遍历**是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220404201713.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220404201713.png) ## 二叉查找树 @@ -129,19 +130,19 @@ permalink: /pages/133326/ **二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。** -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220405172359.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172359.png) ### 二叉查找树的查找 首先,我们看如何在二叉查找树中查找一个节点。我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220405172537.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172537.png) ### 二叉查找树的插入 如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220405172549.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405172549.png) ### 二叉查找树的删除 @@ -149,13 +150,13 @@ permalink: /pages/133326/ 第二种情况是,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220405200219.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200219.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220405200234.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200234.png) 第三种情况是,如果要删除的节点有两个子节点,这就比较复杂了。我们需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220405200456.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405200456.png) ### 二叉查找树的时间复杂度 @@ -163,7 +164,7 @@ permalink: /pages/133326/ 二叉查找树的形态各式各样。比如这个图中,对于同一组数据,我们构造了三种二叉查找树。它们的查找、插入、删除操作的执行效率都是不一样的。图中第一种二叉查找树,根节点的左右子树极度不平衡,已经退化成了链表,所以查找的时间复杂度就变成了 O(n)。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220405234630.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220405234630.png) ### 为什么需要二叉查找树 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" index 70dbdf3cc3..49963229b3 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/02.\345\240\206.md" @@ -1,6 +1,7 @@ --- title: 堆 date: 2015-03-09 16:01:27 +order: 02 categories: - 数据结构和算法 - 树 @@ -31,7 +32,7 @@ permalink: /pages/99ac45/ 完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220311112542.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311112542.jpg) 堆常见的操作: diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" index bb874f2fff..eca2e1c406 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/03.B+\346\240\221.md" @@ -1,6 +1,7 @@ --- title: B+树 date: 2022-03-13 22:37:27 +order: 03 categories: - 数据结构和算法 - 树 @@ -18,11 +19,11 @@ permalink: /pages/2ba2ac/ B+树是在二叉查找树的基础上进行了改造:树中的节点并不存储数据本身,而是只是作为索引。每个叶子节点串在一条链表上,链表中的数据是从小到大有序的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220311092926.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311092926.jpg) 改造之后,如果我们要求某个区间的数据。我们只需要拿区间的起始值,在树中进行查找,当查找到某个叶子节点之后,我们再顺着链表往后遍历,直到链表中的结点数据值大于区间的终止值为止。所有遍历到的数据,就是符合区间值的所有数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220311092929.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220311092929.jpg) 但是,我们要为几千万、上亿的数据构建索引,如果将索引存储在内存中,尽管内存访问的速度非常快,查询的效率非常高,但是,占用的内存会非常多。 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" index c1478f9502..b8593b1d50 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/04.LSM\346\240\221.md" @@ -1,6 +1,7 @@ --- title: LSM树 date: 2022-03-16 09:27:21 +order: 04 categories: - 数据结构和算法 - 树 @@ -30,7 +31,7 @@ LSM 树就是根据这个思路设计了这样一个机制:当数据写入时 可以参考两个有序链表归并排序的过程,将 C0 树和 C1 树的所有叶子节点中存储的数据,看作是两个有序链表,那滚动合并问题就变成了我们熟悉的两个有序链表的归并问题。不过由于涉及磁盘操作,那为了提高写入效率和检索效率,我们还需要针对磁盘的特性,在一些归并细节上进行优化。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220316105440.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316105440.png) 由于磁盘具有顺序读写效率高的特性,因此,为了提高 C1 树中节点的读写性能,除了根节点以外的节点都要尽可能地存放到连续的块中,让它们能作为一个整体单位来读写。这种包含多个节点的块就叫作多页块(Multi-Pages Block)。 @@ -42,7 +43,7 @@ LSM 树就是根据这个思路设计了这样一个机制:当数据写入时 第四步,重复第三步,直到遍历完 C0 树和 C1 树的所有叶子节点,并将所有的归并结果写入到磁盘。这个时候,我们就可以同时删除 C0 树和 C1 树中被处理过的叶子节点。这样就完成了滚动归并的过程。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220316110736.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316110736.png) ### LSM 树是如何检索 @@ -71,7 +72,7 @@ WAL 技术保存和恢复数据的具体步骤如下: 3. 系统会周期性地检查内存中的数据是否都被处理完了(比如,被删除或者写入磁盘),并且生成对应的检查点(Check Point)记录在磁盘中。然后,我们就可以随时删除被处理完的数据了。这样一来,log 文件就不会无限增长了。 4. 系统崩溃重启,我们只需要从磁盘中读取检查点,就能知道最后一次成功处理的数据在 log 文件中的位置。接下来,我们就可以把这个位置之后未被处理的数据,从 log 文件中读出,然后重新加载到内存中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220316104837.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316104837.png) ## 参考资料 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" index 765f6a2573..656ce7e80d 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/05.\345\255\227\345\205\270\346\240\221.md" @@ -1,6 +1,7 @@ --- title: 字典树 date: 2022-03-13 22:37:27 +order: 05 categories: - 数据结构和算法 - 树 @@ -15,19 +16,19 @@ permalink: /pages/eec931/ ## 什么是字典树 -Trie 树(又叫「前缀树」或「字典树」)是一种用于快速查询「某个字符串/字符前缀」是否存在的数据结构。 +Trie 树(又叫“前缀树”或“字典树”)是一种用于快速查询“某个字符串/字符前缀”是否存在的数据结构。 - 根节点(Root)不包含字符,除根节点外的每一个节点都仅包含一个字符; - 从根节点到某一节点路径上所经过的字符连接起来,即为该节点对应的字符串; - 任意节点的所有子节点所包含的字符都不相同; -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220313181057.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181057.jpg) ### 字典树的构造 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220313181243.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181243.jpg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220313181425.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181425.jpg) 构建 Trie 树的过程,需要扫描所有的字符串,时间复杂度是 O(n)(n 表示所有字符串的长度和)。 @@ -47,7 +48,7 @@ Trie 树(又叫「前缀树」或「字典树」)是一种用于快速查询 4. 以此类推,进行迭代过程; 5. 在某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,查找完成。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220313181305.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220313181305.jpg) 每次查询时,如果要查询的字符串长度是 k,那我们只需要比对大约 k 个节点,就能完成查询操作。跟原本那组字符串的长度和个数没有任何关系。所以说,构建好 Trie 树后,在其中查找字符串的时间复杂度是 O(k),k 表示要查找的字符串的长度。 @@ -69,29 +70,29 @@ Trie 树(又叫「前缀树」或「字典树」)是一种用于快速查询 (1)自动补全 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305095300.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305095300.png) (2)拼写检查 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305101637.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305101637.png) (3)IP 路由 (最长前缀匹配) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305102959.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305102959.gif) 图 3. 使用 Trie 树的最长前缀匹配算法,Internet 协议(IP)路由中利用转发表选择路径。 (4)T9 (九宫格) 打字预测 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305103047.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305103047.jpg) (5)单词游戏 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305103052.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305103052.png) Trie 树可通过剪枝搜索空间来高效解决 Boggle 单词游戏 ## 参考资料 - [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) -- https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/ \ No newline at end of file +- https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shi-xian-trie-qian-zhui-shu-by-leetcode/ diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" index c4370cc9e2..7d6477fa09 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/02.\346\240\221/06.\347\272\242\351\273\221\346\240\221.md" @@ -1,6 +1,7 @@ --- title: 红黑树 date: 2018-06-01 21:10:23 +order: 06 categories: - 数据结构和算法 - 树 @@ -20,7 +21,7 @@ permalink: /pages/0966fa/ 完全二叉树、满二叉树其实都是平衡二叉树,但是非完全二叉树也有可能是平衡二叉树。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310202113.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202113.jpg) **平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比较“平衡”,不要出现左子树很高、右子树很矮的情况。这样就能让整棵树的高度相对来说低一些,相应的插入、删除、查找等操作的效率高一些**。 @@ -35,7 +36,7 @@ permalink: /pages/0966fa/ - 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的; - 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点; -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310202612.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202612.jpg) ### 为什么说红黑树是“近似平衡”的? @@ -47,7 +48,7 @@ permalink: /pages/0966fa/ 红色节点删除之后,有些节点就没有父节点了,它们会直接拿这些节点的祖父节点(父节点的父节点)作为父节点。所以,之前的二叉树就变成了四叉树。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310202902.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310202902.jpg) 前面红黑树的定义里有这么一条:从任意节点到可达的叶子节点的每个路径包含相同数目的黑色节点。我们从四叉树中取出某些节点,放到叶节点位置,四叉树就变成了完全二叉树。所以,仅包含黑色节点的四叉树的高度,比包含相同节点个数的完全二叉树的高度还要小。 @@ -87,7 +88,7 @@ AVL 树是一种高度平衡的二叉树,所以查找的效率非常高,但 - 关注节点变成 a 的祖父节点 c; - 跳到 CASE 2 或者 CASE 3。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310203600.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203600.jpg) **CASE 2:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的右子节点**,我们就依次执行下面的操作: @@ -95,7 +96,7 @@ AVL 树是一种高度平衡的二叉树,所以查找的效率非常高,但 - 围绕新的关注节点 b 左旋; - 跳到 CASE 3。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310203623.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203623.jpg) **CASE 3:如果关注节点是 a,它的叔叔节点 d 是黑色,关注节点 a 是其父节点 b 的左子节点**,我们就依次执行下面的操作: @@ -103,7 +104,7 @@ AVL 树是一种高度平衡的二叉树,所以查找的效率非常高,但 - 将关注节点 a 的父节点 b、兄弟节点 c 的颜色互换。 - 调整结束。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310203645.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310203645.jpg) ### 删除操作的平衡调整 @@ -115,7 +116,7 @@ AVL 树是一种高度平衡的二叉树,所以查找的效率非常高,但 - 节点 a 只能是黑色,节点 b 也只能是红色,其他情况均不符合红黑树的定义。这种情况下,我们把节点 b 改为黑色; - 调整结束,不需要进行二次调整。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220310204215.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220310204215.jpg) **CASE 2:如果要删除的节点 a 有两个非空子节点,并且它的后继节点就是节点 a 的右子节点 c**。我们就依次进行下面的操作: diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" index b16ff7967d..505309d98e 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/03.\345\223\210\345\270\214\350\241\250.md" @@ -1,6 +1,7 @@ --- title: 哈希表 date: 2015-03-16 14:19:59 +order: 03 categories: - 数据结构和算法 tags: @@ -31,7 +32,7 @@ permalink: /pages/be34fc/ **哈希表用的是数组支持按照下标随机访问数据的特性,所以哈希表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有哈希表**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320201844.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) 哈希表通过散列函数把元素的键值映射为下标,然后将数据存储在数组中对应下标的位置。按照键值查询元素时,用同样的散列函数,将键值转化数组下标,从对应的数组下标的位置取数据。 @@ -91,7 +92,7 @@ permalink: /pages/be34fc/ **线性探测**(Linear Probing):当我们往哈希表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323200359.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323200359.png) 对于使用线性探测法解决冲突的哈希表,删除操作稍微有些特别。我们不能单纯地把要删除的元素设置为空。这是为什么呢?在查找的时候,一旦我们通过线性探测方法,找到一个空闲位置,我们就可以认定哈希表中不存在这个数据。但是,如果这个空闲位置是我们后来删除的,就会导致原来的查找算法失效。本来存在的数据,会被认定为不存在。这个问题如何解决呢? @@ -107,7 +108,7 @@ permalink: /pages/be34fc/ **基于链表的散列冲突处理方法比较适合存储大对象、大数据量的哈希表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表**。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323200419.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323200419.png) 当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是 O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。那查找或删除操作的时间复杂度是多少呢? diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" index ce575e69d4..713dbbe8bf 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/04.\350\267\263\350\241\250.md" @@ -1,6 +1,7 @@ --- title: 跳表 date: 2020-10-23 09:21:13 +order: 04 categories: - 数据结构和算法 tags: @@ -17,21 +18,21 @@ permalink: /pages/42aedd/ 但是,即使是有序的链表,也只能使用低效的顺序查找,其时间复杂度为 `O(n)`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323113532.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323113532.png) 如何提高链表的查找效率呢? 我们可以对链表加一层索引。具体来说,可以每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作**索引**或**索引层**。索引节点中通过一个 down 指针,指向下一级结点。通过这样的改造,就可以支持类似二分查找的算法。我们把改造之后的数据结构叫作**跳表**(Skip list)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323155309.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155309.png) 随着数据的不断增长,一级索引层也变得越来越长。此时,我们可以为一级索引再增加一层索引层:二级索引层。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323155346.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155346.png) 随着数据的膨胀,当二级索引层也变得很长时,我们可以继续为其添加新的索引层。**这种链表加多级索引的结构,就是跳表**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323114408.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323114408.png) ### 跳表的时间复杂度 @@ -49,7 +50,7 @@ permalink: /pages/42aedd/ 所以,跳表的空间复杂度是 `O(n)`。 -跳表的存储空间其实还有压缩空间。比如,我们增加索引节点的范围,由『每两个节点抽一个上级索引节点』改为『每五个节点抽一个上级索引节点』,可以显著节省存储空间。 +跳表的存储空间其实还有压缩空间。比如,我们增加索引节点的范围,由“每两个节点抽一个上级索引节点”改为“每五个节点抽一个上级索引节点”,可以显著节省存储空间。 实际上,在软件开发中,我们不必太在意索引占用的额外空间。在讲数据结构和算法时,我们习惯性地把要处理的数据看成整数,但是在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。 @@ -61,7 +62,7 @@ permalink: /pages/42aedd/ 跳表不仅支持查找操作,还支持动态的插入、删除操作,而且插入、删除操作的时间复杂度也是 `O(logn)`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323155933.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323155933.png) - **插入操作**:对于纯粹的单链表,需要遍历每个结点,来找到插入的位置。但是,对于跳表来说,我们讲过查找某个结点的的时间复杂度是 `O(log n)`,所以这里查找某个数据应该插入的位置,方法也是类似的,时间复杂度也是 `O(log n)`。 - **删除操作**:如果这个结点在索引中也有出现,我们除了要删除原始链表中的结点,还要删除索引中的。因为单链表中的删除操作需要拿到要删除结点的前驱结点,然后通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点。当然,如果我们用的是双向链表,就不需要考虑这个问题了。 @@ -70,7 +71,7 @@ permalink: /pages/42aedd/ 当我们不停地往跳表中插入数据时,如果我们不更新索引,就有可能出现某 2 个索引结点之间数据非常多的情况。极端情况下,跳表还会退化成单链表。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220323161942.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220323161942.png) 如红黑树、AVL 树这样的平衡二叉树,是通过左右旋的方式保持左右子树的大小平衡,而跳表是通过随机函数来维护前面提到的“平衡性”。 @@ -100,4 +101,4 @@ Redis 中的有序集合支持的核心操作主要有下面这几个: ## 参考资料 -- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) \ No newline at end of file +- [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" index 536193cf3a..b2d9e6c3df 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/05.\345\233\276.md" @@ -1,6 +1,7 @@ --- title: 图 date: 2015-03-24 15:31:13 +order: 05 categories: - 数据结构和算法 tags: @@ -13,7 +14,7 @@ permalink: /pages/5dd75b/ 在计算机科学中,一个图就是一些*顶点*的集合,这些顶点通过一系列*边*结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/data-structure/graph/graph.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/data-structure/graph/graph.png) ## 什么是图 @@ -33,7 +34,7 @@ permalink: /pages/5dd75b/ 如果图的边没有方向性,则被成为无向图。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220314093554.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220314093554.jpg) ## 图的基本操作 diff --git "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/README.md" "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/README.md" index a3c611deb0..0351ca5036 100644 --- "a/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/README.md" +++ "b/source/_posts/11.\346\225\260\346\215\256\347\273\223\346\236\204\345\222\214\347\256\227\346\263\225/README.md" @@ -7,11 +7,12 @@ tags: - 数据结构和算法 permalink: /pages/3ccbd4/ hidden: true +index: false ---

- logo + logo

@@ -46,7 +47,7 @@ hidden: true ## 📖 内容 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200702071922.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200702071922.png) - 综合 - [数据结构和算法指南](00.综合/01.数据结构和算法指南.md) @@ -235,4 +236,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" index 087e9e2f5f..149e889e8d 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/01.Nosql\346\212\200\346\234\257\351\200\211\345\236\213.md" @@ -1,6 +1,7 @@ --- title: Nosql技术选型 date: 2020-02-09 02:18:58 +order: 01 categories: - 数据库 - 数据库综合 @@ -13,7 +14,7 @@ permalink: /pages/0e1012/ # Nosql 技术选型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209020702.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209020702.png) ## 一、Nosql 简介 @@ -27,7 +28,7 @@ permalink: /pages/0e1012/ 随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求。传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多能以克服的问题。由此,各种各样的 NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。 -![nosql-history](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005228.png) +![nosql-history](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005228.png) **NoSQL,泛指非关系型的数据库**,可以理解为 SQL 的一个有力补充。 @@ -56,7 +57,7 @@ permalink: /pages/0e1012/ 将表放入存储系统中有两种方法,而我们绝大部分是采用行存储的。 行存储法是将各行放入连续的物理位置,这很像传统的记录和文件系统。 列存储法是将数据按照列存储到数据库中,与行存储类似,下图是两种存储方法的图形化解释: -![按行存储和按列存储模式](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005316.png) +![按行存储和按列存储模式](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005316.png) ### 列式数据库产品 @@ -80,13 +81,13 @@ permalink: /pages/0e1012/ 列式数据库由于其针对不同列的数据特征而发明的不同算法,使其**往往有比行式数据库高的多的压缩率**,普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右。 比较常见的,通过字典表压缩数据: 下面中才是那张表本来的样子。经过字典表进行数据压缩后,表中的字符串才都变成数字了。正因为每个字符串在字典表里只出现一次了,所以达到了压缩的目的(有点像规范化和非规范化 Normalize 和 Denomalize) -![通过字典表压缩数据](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005406.png) +![通过字典表压缩数据](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005406.png) - **查询效率高** 读取多条数据的同一列效率高,因为这些列都是存储在一起的,一次磁盘操作可以数据的指定列全部读取到内存中。 下图通过一条查询的执行过程说明列式存储(以及数据压缩)的优点 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209005611.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209005611.png) ``` 执行步骤如下: @@ -127,19 +128,19 @@ KV 存储非常适合存储**不涉及过多数据关系业务关系的数据** - Redis - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209010410.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209010410.png) Redis 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。从 2015 年 6 月开始,Redis 的开发由 Redis Labs 赞助,而 2013 年 5 月至 2015 年 6 月期间,其开发由 Pivotal 赞助。在 2013 年 5 月之前,其开发由 VMware 赞助。根据月度排行网站 DB-Engines.com 的数据显示,Redis 是最流行的键值对存储数据库。 - Cassandra - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209010451.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209010451.png) Apache Cassandra(社区内一般简称为 C\*)是一套开源分布式 NoSQL 数据库系统。它最初由 Facebook 开发,用于储存收件箱等简单格式数据,集 Google BigTable 的数据模型与 Amazon Dynamo 的完全分布式架构于一身。Facebook 于 2008 将 Cassandra 开源,此后,由于 Cassandra 良好的可扩展性和性能,被 Apple, Comcast,Instagram, Spotify, eBay, Rackspace, Netflix 等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。 - LevelDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209011140.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209011140.png) LevelDB 是一个由 Google 公司所研发的键/值对(Key/Value Pair)嵌入式数据库管理系统编程库, 以开源的 BSD 许可证发布。 @@ -176,13 +177,13 @@ KV 存储非常适合存储**不涉及过多数据关系业务关系的数据** - MongoDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209012320.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209012320.png) **MongoDB**是一种面向文档的数据库管理系统,由 C++ 撰写而成,以此来解决应用程序开发社区中的大量现实问题。2007 年 10 月,MongoDB 由 10gen 团队所发展。2009 年 2 月首度推出。 - CouchDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209012418.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209012418.png) Apache CouchDB 是一个开源数据库,专注于易用性和成为"**完全拥抱 web 的数据库**"。它是一个使用 JSON 作为存储格式,JavaScript 作为查询语言,MapReduce 和 HTTP 作为 API 的 NoSQL 数据库。其中一个显著的功能就是多主复制。CouchDB 的第一个版本发布在 2005 年,在 2008 年成为了 Apache 的项目。 @@ -234,11 +235,11 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 现在有如下文档集合: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014530.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014530.png) 正排索引得到索引如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014723.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014723.png) 可见,正排索引适用于根据文档名称查询文档内容 @@ -248,7 +249,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 带有单词频率信息的倒排索引如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014842.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014842.png) 可见,倒排索引适用于根据关键词来查询文档内容 @@ -262,7 +263,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 - Solr - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209014947.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209014947.png) Solr 是 Apache Lucene 项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如 Word、PDF)的处理。Solr 是高度可扩展的,并提供了分布式搜索和索引复制 @@ -296,7 +297,7 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 ## 六、图数据库 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015751.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015751.png) **图形数据库应用图论存储实体之间的关系信息**。最常见例子就是社会网络中人与人之间的关系。关系型数据库用于存储“关系型”数据的效果并不好,其查询复杂、缓慢、超出预期,而图形数据库的独特设计恰恰弥补了这个缺陷,解决关系型数据库存储和处理复杂关系型数据功能较弱的问题。 @@ -304,19 +305,19 @@ MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持 - Neo4j - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015817.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015817.png) Neo4j 是由 Neo4j,Inc。开发的图形数据库管理系统。由其开发人员描述为具有原生图存储和处理的符合 ACID 的事务数据库,根据 DB-Engines 排名, Neo4j 是最流行的图形数据库。 - ArangoDB - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015858.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015858.png) ArangoDB 是由 triAGENS GmbH 开发的原生多模型数据库系统。数据库系统支持三个重要的数据模型(键/值,文档,图形),其中包含一个数据库核心和统一查询语言 AQL(ArangoDB 查询语言)。查询语言是声明性的,允许在单个查询中组合不同的数据访问模式。ArangoDB 是一个 NoSQL 数据库系统,但 AQL 在很多方面与 SQL 类似。 - Titan - ![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200209015923.png) + ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200209015923.png) Titan 是一个可扩展的图形数据库,针对存储和查询包含分布在多机群集中的数百亿个顶点和边缘的图形进行了优化。Titan 是一个事务性数据库,可以支持数千个并发用户实时执行复杂的图形遍历。 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" index 5c728d0ee6..cc851ee7f0 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/02.\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" @@ -1,6 +1,7 @@ --- title: 数据结构与数据库索引 date: 2022-03-27 23:39:10 +order: 02 categories: - 数据库 - 数据库综合 @@ -41,13 +42,13 @@ permalink: /pages/d7cd88/ 在有序数组上应用二分查找法如此高效,为什么几乎没有数据库直接使用数组作为索引?这是因为它的限制条件:**数据有序**——为了保证数据有序,每次添加、删除数组数据时,都必须要进行数据调整,来保证其有序,而 **数组的插入/删除操作,时间复杂度为 `O(n)`**。此外,由于数组空间大小固定,每次扩容只能采用复制数组的方式。数组的这些特性,决定了它不适合用于数据频繁变化的应用场景。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320115836.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320115836.png) **链表用不连续的内存空间来存储数据;并通过一个指针按顺序将这些空间串起来,形成一条链**。 -区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为「结点」复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,**链表的插入/删除操作,时间复杂度为 `O(1)`**,但是,链表只支持顺序访问,其 **查找时间复杂度为 `O(n)`**。其低效的查找方式,决定了链表不适合作为索引。 +区别于数组,链表中的元素不是存储在内存中连续的一片区域,链表中的数据存储在每一个称之为“结点”复合区域里,在每一个结点除了存储数据以外,还保存了到下一个节点的指针(Pointer)。由于不必按顺序存储,**链表的插入/删除操作,时间复杂度为 `O(1)`**,但是,链表只支持顺序访问,其 **查找时间复杂度为 `O(n)`**。其低效的查找方式,决定了链表不适合作为索引。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320174829.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320174829.png) ## 哈希索引 @@ -55,7 +56,7 @@ permalink: /pages/d7cd88/ **哈希表** 使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。哈希表的本质是一个数组,其思路是:使用 Hash 函数将 Key 转换为数组下标,利用数组的随机访问特性,使得我们能在 `O(1)` 的时间代价内完成检索。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320201844.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) 有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 @@ -64,7 +65,7 @@ permalink: /pages/d7cd88/ 哈希索引基于哈希表实现,**只适用于等值查询**。对于每一行数据,哈希索引都会将所有的索引列计算一个哈希码(`hashcode`),哈希码是一个较小的值。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。 -✔ 哈希索引的**优点**: +✔️️️ 哈希索引的**优点**: - 因为索引数据结构紧凑,所以**查询速度非常快**。 @@ -105,7 +106,7 @@ B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀 - 第一,所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 - 其次,所有的叶子节点由指针连接。如下图为简化了的`B+Tree`。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200304235424.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg) 根据叶子节点的内容,索引类型分为主键索引和非主键索引。 @@ -214,4 +215,4 @@ LSM 树的这些特点,使得它相对于 B+ 树,在写入性能上有大幅 - [数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) - [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) - [Data Structures for Databases](https://www.cise.ufl.edu/~mschneid/Research/papers/HS05BoCh.pdf) -- [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) \ No newline at end of file +- [Data Structures and Algorithms for Big Databases](https://people.csail.mit.edu/bradley/BenderKuszmaul-tutorial-xldb12.pdf) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" index d2b801e8c9..42a7a801e6 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/01.\346\225\260\346\215\256\345\272\223\347\273\274\345\220\210/README.md" @@ -9,6 +9,7 @@ tags: - 综合 permalink: /pages/3c3c45/ hidden: true +index: false --- # 数据库综合 @@ -22,4 +23,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" index ed2deacebd..17a90468dc 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/01.ShardingSphere\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: ShardingSphere 简介 date: 2020-10-08 20:30:30 +order: 01 categories: - 数据库 - 数据库中间件 @@ -20,7 +21,7 @@ permalink: /pages/5ed2a2/ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这 3 款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151613.png) #### ShardingSphere-JDBC @@ -30,7 +31,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 - 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 - 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151213.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151213.png) #### Sharding-Proxy @@ -39,7 +40,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 - 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用。 - 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151434.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151434.png) #### Sharding-Sidecar(TODO) @@ -47,7 +48,7 @@ ShardingSphere 是一套开源的分布式数据库中间件解决方案组成 Database Mesh 的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,是将杂乱无章的应用与数据库之间的交互进行有效地梳理。 使用 Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151557.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151557.png) | _Sharding-JDBC_ | _Sharding-Proxy_ | _Sharding-Sidecar_ | | | :-------------- | :--------------- | :----------------- | ------ | @@ -64,7 +65,7 @@ ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能 Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151658.png) ### 功能列表 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" index dab4e33075..55202fc0d4 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/01.Shardingsphere/02.ShardingSphereJdbc.md" @@ -1,6 +1,7 @@ --- title: ShardingSphere Jdbc date: 2020-12-28 00:01:28 +order: 02 categories: - 数据库 - 数据库中间件 @@ -22,7 +23,7 @@ shardingsphere-jdbc 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供 - 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 - 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008151213.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008151213.png) ## 快速入门 @@ -88,7 +89,7 @@ DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(dataSou ShardingSphere 的 3 个产品的数据分片主要流程是完全一致的。 核心由 `SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并`的流程组成。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201008153551.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201008153551.png) - QL 解析:分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。 - 执行器优化:合并和优化分片条件,如 OR 等。 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" index 3c2a94aad2..7dc02e4c59 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/02.Flyway.md" @@ -1,6 +1,7 @@ --- title: 版本管理中间件 Flyway date: 2019-08-22 09:02:39 +order: 02 categories: - 数据库 - 数据库中间件 @@ -397,7 +398,7 @@ migrations 最常用的编写形式就是 SQL。 为了被 Flyway 自动识别,SQL migrations 的文件命名必须遵循规定的模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/flyway/sql-migrations.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/flyway/sql-migrations.png) - **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置) - **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要) @@ -416,7 +417,7 @@ migrations 最常用的编写形式就是 SQL。 为了被 Flyway 自动识别,JAVA migrations 的文件命名必须遵循规定的模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/flyway/java-migrations.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/flyway/java-migrations.png) - **Prefix** - `V` 代表 versioned migrations (可配置), `U` 代表 undo migrations (可配置)、 `R` 代表 repeatable migrations (可配置) - **Version** - 版本号通过`.`(点)或`_`(下划线)分隔 (repeatable migrations 不需要) @@ -499,6 +500,6 @@ Flyway 的功能主要围绕着 7 个基本命令:[Migrate](https://flywaydb.o | [Github](https://github.com/flyway/flyway) | [官方文档](https://flywaydb.org/) | -## :door: 传送门 +## 🚪 传送 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" index cdadf2f48e..f3f49ff801 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/02.\346\225\260\346\215\256\345\272\223\344\270\255\351\227\264\344\273\266/README.md" @@ -1,5 +1,5 @@ --- -title: 数据库中间件和代理 +title: 数据库中间件 date: 2022-04-11 16:52:35 categories: - 数据库 @@ -9,9 +9,10 @@ tags: - 中间件 permalink: /pages/addb05/ hidden: true +index: false --- -# 数据库中间件和代理 +# 数据库中间件 ## 📖 内容 @@ -30,4 +31,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" deleted file mode 100644 index dc959f2efa..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\351\235\242\350\257\225.md" +++ /dev/null @@ -1,1157 +0,0 @@ ---- -title: 关系型数据库面试 -date: 2020-01-15 23:21:02 -categories: - - 数据库 - - 关系型数据库 - - 综合 -tags: - - 数据库 - - 关系型数据库 - - 面试 -permalink: /pages/9bb28f/ ---- - -# 关系型数据库面试 - -## 索引和约束 - -### 什么是索引 - -索引是对数据库表中一或多个列的值进行排序的结构,是帮助数据库高效查询数据的数据结构。 - -### 索引的优缺点 - -✔ 索引的优点: - -- 索引大大减少了服务器需要扫描的数据量,从而加快检索速度。 -- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。使用索引可以减少访问的行数,从而减少锁的竞争,提高并发。 -- 索引可以帮助服务器避免排序和临时表。 -- 索引可以将随机 I/O 变为顺序 I/O。 -- 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。 - -❌ 索引的缺点: - -- 创建和维护索引要耗费时间,这会随着数据量的增加而增加。 -- **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 -- 写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低。 - -### 何时使用索引 - -索引能够轻易将查询性能提升几个数量级。 - -✔ 什么情况**适用**索引: - -- 表经常进行 `SELECT` 操作; -- 表的数据量比较大; -- 列名经常出现在 `WHERE` 或连接(`JOIN`)条件中 - -❌ 什么情况**不适用**索引: - -- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` )- 需要更新索引空间; -- **非常小的表** - 对于非常小的表,大部分情况下简单的全表扫描更高效。 -- 列名不经常出现在 `WHERE` 或连接(`JOIN`)条件中 - 索引就会经常不命中,没有意义,还增加空间开销。 -- 对于特大型表,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 - -### 索引的类型 - -主流的关系型数据库一般都支持以下索引类型: - -从逻辑类型上划分(即一般创建表时设置的索引类型): - -- 唯一索引(`UNIQUE`):索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。 -- 主键索引(`PRIMARY`):一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。 -- 普通索引(`INDEX`):最基本的索引,没有任何限制。 -- 组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。 - -从物理存储上划分: - -- **聚集索引**(`Clustered`):表中各行的物理顺序与键值的逻辑(索引)顺序相同,每个表只能有一个。 -- **非聚集索引**(`Non-clustered`):非聚集索引指定表的逻辑顺序,也可以视为二级索引。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于 249 个。 - -### 索引的数据结构 - -主流数据库的索引一般使用的数据结构为:B 树、B+ 树。 - -#### B 树 - -一棵 M 阶的 B-Tree 满足以下条件: - -- 每个结点至多有 M 个孩子; -- 除根结点和叶结点外,其它每个结点至少有 M/2 个孩子; -- 根结点至少有两个孩子(除非该树仅包含一个结点); -- 所有叶结点在同一层,叶结点不包含任何关键字信息; -- 有 K 个关键字的非叶结点恰好包含 K+1 个孩子; - -对于任意结点,其内部的关键字 Key 是升序排列的。每个节点中都包含了 data。 - -
- -
- -对于每个结点,主要包含一个关键字数组 `Key[]`,一个指针数组(指向儿子)`Son[]`。 - -在 B-Tree 内,查找的流程是: - -1. 使用顺序查找(数组长度较短时)或折半查找方法查找 Key[] 数组,若找到关键字 K,则返回该结点的地址及 K 在 Key[] 中的位置; -2. 否则,可确定 K 在某个 Key[i] 和 Key[i+1] 之间,则从 Son[i] 所指的子结点继续查找,直到在某结点中查找成功; -3. 或直至找到叶结点且叶结点中的查找仍不成功时,查找过程失败。 - -#### B+ 树 - -B+Tree 是 B-Tree 的变种: - -- 每个节点的指针上限为 2d 而不是 2d+1(d 为节点的出度)。 -- 非叶子节点不存储 data,只存储 key;叶子节点不存储指针。 - -
- -
- -由于并不是所有节点都具有相同的域,因此 B+Tree 中叶节点和内节点一般大小不同。这点与 B-Tree 不同,虽然 B-Tree 中不同节点存放的 key 和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中 B-Tree 往往对每个节点申请同等大小的空间。 - -**带有顺序访问指针的 B+Tree** - -一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 的基础上进行了优化,增加了顺序访问指针。 - -
- -
- -在 B+Tree 的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的 B+Tree。 - -这个优化的目的是为了提高区间访问的性能,例如上图中如果要查询 key 为从 18 到 49 的所有数据记录,当找到 18 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。 - -#### B 树 vs. B+ 树 - -- B+ 树更适合外部存储(一般指磁盘存储),由于内节点(非叶子节点)不存储 data,所以一个节点可以存储更多的内节点,每个节点能索引的范围更大更精确。也就是说使用 B+ 树单次磁盘 IO 的信息量相比较 B 树更大,IO 效率更高。 -- Mysql 是关系型数据库,经常会按照区间来访问某个索引列,B+ 树的叶子节点间按顺序建立了链指针,加强了区间访问性,所以 B+ 树对索引列上的区间范围查询很友好。而 B 树每个节点的 key 和 data 在一起,无法进行区间查找。 - -#### Hash - -> Hash 索引只有精确匹配索引所有列的查询才有效。 - -对于每一行数据,对所有的索引列计算一个 `hashcode`。哈希索引将所有的 `hashcode` 存储在索引中,同时在 Hash 表中保存指向每个数据行的指针。 - -哈希结构索引的优点: - -- 因为索引数据结构紧凑,所以查询速度非常快。 - -哈希结构索引的缺点: - -- 哈希索引数据不是按照索引值顺序存储的,所以无法用于排序。 -- 哈希索引不支持部分索引匹配查找。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 -- 哈希索引只支持等值比较查询,不支持任何范围查询,如 WHERE price > 100。 -- 哈希索引有可能出现哈希冲突,出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 - -### 索引策略 - -#### 索引基本原则 - -- 索引不是越多越好,不要为所有列都创建索引。 -- 要尽量避免冗余和重复索引; -- 要考虑删除未使用的索引; -- 尽量的扩展索引,不要新建索引; -- 频繁作为 `WHERE` 过滤条件的列应该考虑添加索引 - -#### 独立的列 - -**如果查询中的列不是独立的列,则数据库不会使用索引**。 - -“独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数。 - -❌ 错误示例: - -```sql -SELECT actor_id FROM actor WHERE actor_id + 1 = 5; -SELECT ... WHERE TO_DAYS(current_date) - TO_DAYS(date_col) <= 10; -``` - -#### 前缀索引和索引选择性 - -有时候需要索引很长的字符列,这会让索引变得大且慢。 - -解决方法是:可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。 - -索引的选择性是指:不重复的索引值和数据表记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 - -对于 BLOB/TEXT/VARCHAR 这种文本类型的列,必须使用前缀索引,因为数据库往往不允许索引这些列的完整长度。 - -要选择足够长的前缀以保证较高的选择性,同时又不能太长(节约空间)。 - -❌ 低效示例: - -```sql -SELECT COUNT(*) AS cnt, city FROM sakila.city_demo -GROUP BY city ORDER BY cnt DESC LIMIT 10; -``` - -✔ 高效示例: - -```sql -SELECT COUNT(*) AS cnt, LEFT(city, 3) AS pref FROM sakila.city_demo -GROUP BY city ORDER BY cnt DESC LIMIT 10; -``` - -#### 多列索引 - -**不要为每个列都创建独立索引**。 - -**将选择性高的列或基数大的列优先排在多列索引最前列**。但有时,也需要考虑 WHERE 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。 - -举例来说,有一张 user 表,其中含 name, sex, age 三个列,如果将这三者组合为多列索引,应该用什么样的顺序呢?从选择性高的角度来看:`name > age > sex`。 - -#### 聚簇索引 - -聚簇索引不是一种单独的索引类型,而是一种数据存储方式。具体细节依赖于实现方式。如 **InnoDB 的聚簇索引实际是在同一个结构中保存了 B 树的索引和数据行**。 - -**聚簇表示数据行和相邻的键值紧凑地存储在一起,因为数据紧凑,所以访问快**。因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。 - -若没有定义主键,InnoDB 会隐式定义一个主键来作为聚簇索引。 - -#### 覆盖索引 - -索引包含所有需要查询的字段的值。 - -具有以下优点: - -- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。 -- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。 -- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。 - -#### 使用索引扫描来做排序 - -Mysql 有两种方式可以生成排序结果:通过排序操作;或者按索引顺序扫描。 - -**索引最好既满足排序,又用于查找行**。这样,就可以使用索引来对结果排序。 - -#### 最左前缀匹配原则 - -MySQL 会一直向右匹配直到遇到范围查询 `(>,<,BETWEEN,LIKE)` 就停止匹配。 - -- 索引可以简单如一个列(a),也可以复杂如多个列(a, b, c, d),即**联合索引**。 -- 如果是联合索引,那么 key 也由多个列组成,同时,索引只能用于查找 key 是否**存在(相等)**,遇到范围查询(>、<、between、like 左匹配)等就**不能进一步匹配**了,后续退化为线性查找。 -- 因此,**列的排列顺序决定了可命中索引的列数**。 - -例子: - -- 如有索引(a, b, c, d),查询条件 a = 1 and b = 2 and c > 3 and d = 4,则会在每个节点依次命中 a、b、c,无法命中 d。(很简单:索引命中只能是**相等**的情况,不能是范围匹配) - -#### = 和 in 可以乱序 - -**不需要考虑=、in 等的顺序**,Mysql 会自动优化这些条件的顺序,以匹配尽可能多的索引列。 - -例子:如有索引(a, b, c, d),查询条件 c > 3 and b = 2 and a = 1 and d < 4 与 a = 1 and c > 3 and b = 2 and d < 4 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c。 - -### 约束 - -数据库约束(`CONSTRAINT`)有哪些: - -- `NOT NULL` - 用于控制字段的内容一定不能为空(NULL)。 -- `UNIQUE` - 字段内容不能重复,一个表允许有多个 `Unique` 约束。 -- `PRIMARY KEY` - 数据表中对储存数据对象予以唯一和完整标识的数据列或属性的组合,它在一个表中只允许有一个。主键的取值不能为空值(Null)。 -- `FOREIGN KEY` - 在一个表中存在的另一个表的主键称此表的外键。用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。 -- `CHECK` - 用于控制字段的值范围。 - -## 并发控制 - -### 乐观锁和悲观锁 - -> - 数据库的乐观锁和悲观锁是什么? -> - 数据库的乐观锁和悲观锁如何实现? - -确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。** - -- **`悲观锁`** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作 - - **在查询完数据的时候就把事务锁起来,直到提交事务(COMMIT)** - - 实现方式:使用数据库中的锁机制 -- **`乐观锁`** - 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 - - **在修改数据的时候把事务锁起来,通过 version 的方式来进行锁定** - - 实现方式:使用 version 版本或者时间戳 - -### 行级锁和表级锁 - -> - 什么是行级锁和表级锁? -> - 什么时候用行级锁?什么时候用表级锁? - -从数据库的锁粒度来看,MySQL 中提供了两种封锁粒度:行级锁和表级锁。 - -- **表级锁(table lock)** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。 -- **行级锁(row lock)** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。 - -二者需要权衡: - -- **锁定的数据量越少,锁竞争的发生频率就越小,系统的并发程度就越高**。 -- **锁粒度越小,系统开销就越大**。 - -在 `InnoDB` 中,行锁是通过给索引上的索引项加锁来实现的。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。 - -### 读写锁 - -> - 什么是读写锁? - -- 独享锁(Exclusive),简写为 X 锁,又称写锁。使用方式:`SELECT ... FOR UPDATE;` -- 共享锁(Shared),简写为 S 锁,又称读锁。使用方式:`SELECT ... LOCK IN SHARE MODE;` - -写锁和读锁的关系,简言之:**独享锁存在,其他事务就不能做任何操作**。 - -**`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁**。 - -### 意向锁 - -> - 什么是意向锁? -> - 意向锁有什么用? - -意向锁的作用是:**当存在表级锁和行级锁的情况下,必须先申请意向锁(表级锁,但不是真的加锁),再获取行级锁**。使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 - -**意向锁是 `InnoDB` 自动加的,不需要用户干预**。 - -### MVCC - -> 什么是 MVCC? -> -> MVCC 有什么用?解决了什么问题? -> -> MVCC 的原理是什么? - -多版本并发控制(Multi-Version Concurrency Control, MVCC)是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 - -MVCC 的思想是: - -- 保存数据在某个时间点的快照。**写操作(DELETE、INSERT、UPDATE)更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系**,这一点和 `CopyOnWrite` 类似。 -- 脏读和不可重复读最根本的原因是**事务读取到其它事务未提交的修改**。在事务进行读取操作时,为了解决脏读和不可重复读问题,**MVCC 规定只能读取已经提交的快照**。当然一个事务可以读取自身未提交的快照,这不算是脏读。 - -### Next-key 锁 - -Next-Key 锁是 MySQL 的 `InnoDB` 存储引擎的一种锁实现。 - -MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题**。在可重复读(`REPEATABLE READ`)隔离级别下,使用 **MVCC + Next-Key 锁** 可以解决幻读问题。 - -另外,根据针对 SQL 语句检索条件的不同,加锁又有以下三种情形需要我们掌握。 - -- `Record Lock` - **行锁对索引项加锁,若没有索引则使用表锁**。 -- `Gap Lock` - 对索引项之间的间隙加锁。锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。`SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;` -- `Next-key lock` -它是 `Record Lock` 和 `Gap Lock` 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。它锁定一个前开后闭区间。 - -索引分为主键索引和非主键索引两种,如果一条 SQL 语句操作了主键索引,MySQL 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL 会先锁定该非主键索引,再锁定相关的主键索引。在 `UPDATE`、`DELETE` 操作时,MySQL 不仅锁定 `WHERE` 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 `next-key lock`。 - -当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。 - -## 事务 - -> 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。具体来说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库事务.png) - -### ACID - -ACID — 数据库事务正确执行的四个基本要素: - -- **原子性(Atomicity)** -- **一致性(Consistency)** -- **隔离性(Isolation)** -- **持久性(Durability)** - -**一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。** - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) - -### 并发一致性问题 - -在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。 - -- **丢失修改** - -T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-丢失修改.png) - -- **脏读** - -T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-脏数据.png) - -- **不可重复读** - -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-不可重复读.png) - -- **幻读** - -T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-幻读.png) - -并发一致性解决方案: - -产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。 - -并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。 - -### 事务隔离 - -数据库隔离级别: - -- **`未提交读(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。 -- **`提交读(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 -- **`重复读(REPEATABLE READ)`** - 保证在同一个事务中多次读取同样数据的结果是一样的。 -- **`串行化(SERIALIXABLE)`** - 强制事务串行执行。 - -数据库隔离级别解决的问题: - -| 隔离级别 | 脏读 | 不可重复读 | 幻读 | -| :------: | :--: | :--------: | :--: | -| 未提交读 | ❌ | ❌ | ❌ | -| 提交读 | ✔️ | ❌ | ❌ | -| 可重复读 | ✔️ | ✔️ | ❌ | -| 可串行化 | ✔️ | ✔️ | ✔️ | - -### 分布式事务 - -在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为 **本地事务**。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 - -**分布式事务** 是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。 - -#### 两阶段提交 - -两阶段提交(XA)对业务侵入很小。 它最大的优势就是对使用方透明,用户可以像使用本地事务一样使用基于 XA 协议的分布式事务。 XA 协议能够严格保障事务 `ACID` 特性。 - -严格保障事务 `ACID` 特性是一把双刃剑。 事务执行在过程中需要将所需资源全部锁定,它更加适用于执行时间确定的短事务。 对于长事务来说,整个事务进行期间对数据的独占,将导致对热点数据依赖的业务系统并发性能衰退明显。 因此,在高并发的性能至上场景中,基于 XA 协议的分布式事务并不是最佳选择。 - -#### 柔性事务 - -如果将实现了`ACID` 的事务要素的事务称为刚性事务的话,那么基于`BASE`事务要素的事务则称为柔性事务。 `BASE`是基本可用、柔性状态和最终一致性这三个要素的缩写。 - -- 基本可用(Basically Available)保证分布式事务参与方不一定同时在线。 -- 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。 -- 而最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。 - -在`ACID`事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。 柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。 - -基于`ACID`的强一致性事务和基于`BASE`的最终一致性事务都不是银弹,只有在最适合的场景中才能发挥它们的最大长处。 可通过下表详细对比它们之间的区别,以帮助开发者进行技术选型。 - -#### 事务方案对比 - -| | 本地事务 | 两(三)阶段事务 | 柔性事务 | -| :------- | :--------------- | :--------------- | --------------- | -| 业务改造 | 无 | 无 | 实现相关接口 | -| 一致性 | 不支持 | 支持 | 最终一致 | -| 隔离性 | 不支持 | 支持 | 业务方保证 | -| 并发性能 | 无影响 | 严重衰退 | 略微衰退 | -| 适合场景 | 业务方处理不一致 | 短事务 & 低并发 | 长事务 & 高并发 | - -## 分库分表 - -### 什么是分库分表 - -> 什么是分库分表?什么是垂直拆分?什么是水平拆分?什么是 Sharding? -> -> 分库分表是为了解决什么问题? -> -> 分库分表有什么优点? -> -> 分库分表有什么策略? - -分库分表的基本思想就是:把原本完整的数据切分成多个部分,放到不同的数据库或表上。 - -分库分表一定是为了支撑 **高并发、数据量大**两个问题的。 - -#### 垂直切分 - -> **垂直切分**,是 **把一个有很多字段的表给拆分成多个表,或者是多个库上去**。一般来说,会 **将较少的、访问频率较高的字段放到一个表里去**,然后 **将较多的、访问频率较低的字段放到另外一个表里去**。因为数据库是有缓存的,访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。 - -![image-20200114211639899](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211639899.png) - -一般来说,满足下面的条件就可以考虑扩容了: - -- Mysql 单库超过 5000 万条记录,Oracle 单库超过 1 亿条记录,DB 压力就很大。 -- 单库超过每秒 2000 个并发时,而一个健康的单库最好保持在每秒 1000 个并发左右,不要太大。 - -在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。 - -#### 水平拆分 - -> **水平拆分** 又称为 **Sharding**,它是将同一个表中的记录拆分到多个结构相同的表中。当 **单表数据量太大** 时,会极大影响 **SQL 执行的性能** 。分表是将原来一张表的数据分布到数据库集群的不同节点上,从而缓解单点的压力。 - -![image-20200114211203589](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211203589.png) - -一般来说,**单表有 200 万条数据** 的时候,性能就会相对差一些了,需要考虑分表了。但是,这也要视具体情况而定,可能是 100 万条,也可能是 500 万条,SQL 越复杂,就最好让单表行数越少。 - -#### 分库分表的优点 - -| # | 分库分表前 | 分库分表后 | -| ------------ | ---------------------------- | -------------------------------------------- | -| 并发支撑情况 | 单机部署,扛不住高并发 | 从单机到多机,能承受的并发增加了多倍 | -| 磁盘使用情况 | 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 | -| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 | - -#### 分库分表策略 - -- 哈希取模:`hash(key) % N` 或 `id % N` - - 优点:可以平均分配每个库的数据量和请求压力(负载均衡)。 - - 缺点:扩容麻烦,需要数据迁移。 -- 范围:可以按照 ID 或时间划分范围。 - - 优点:扩容简单。 - - 缺点:这种策略容易产生热点问题。 -- 映射表:使用单独的一个数据库来存储映射关系。 - - 缺点:存储映射关系的数据库也可能成为性能瓶颈,且一旦宕机,分库分表的数据库就无法工作。所以不建议使用这种策略。 - - 优点:扩容简单,可以解决分布式 ID 问题。 - -### 分库分表中间件 - -> ❓ 常见问题: -> -> - 你用过哪些分库分表中间件,简单介绍一下? -> -> - 不同的分库分表中间件各自有什么特性,有什么优缺点? -> -> - 分库分表中间件技术如何选型? - -#### 常见的分库分表中间件 - -- [Cobar](https://github.com/alibaba/cobar) - 阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 cobar 集群,cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。 -- [TDDL](https://github.com/alibaba/tb_tddl) - 淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。 -- [Atlas](https://github.com/Qihoo360/Atlas) - 360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。 -- [sharding-jdbc](https://github.com/dangdangdotcom/sharding-jdbc) - 当当开源的,属于 client 层方案。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且目前推出到了 2.0 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也**可以选择的方案**。 -- [Mycat](http://www.mycat.org.cn/) - 基于 cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 sharding jdbc 来说,年轻一些,经历的锤炼少一些。 - -#### 分库分表中间件技术选型 - -建议使用的是 sharding-jdbc 和 mycat。 - -- [sharding-jdbc](https://github.com/dangdangdotcom/sharding-jdbc) 这种 client 层方案的**优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高**,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要**耦合** sharding-jdbc 的依赖。其本质上通过配置多数据源,然后根据设定的分库分表策略,计算路由,将请求发送到计算得到的节点上。 - -- [Mycat](http://www.mycat.org.cn/) 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套中间件,运维成本高,但是**好处在于对于各个项目是透明的**,如果遇到升级之类的都是自己中间件那里搞就行了。 - -通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用 sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用 mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 mycat,然后大量项目直接透明使用即可。 - -### 分库分表的问题 - -> - 分库分表的常见问题有哪些? -> -> - 你是如何解决分库分表的问题的? -> -> 下文一一讲解常见分库分表的问题及解决方案。 - -#### 分布式事务 - -方案一:使用数据库事务 - -- 优点:交由数据库管理,简单有效 -- 缺点:性能代价高,特别是 shard 越来越多时 - -方案二:由应用程序和数据库共同控制 - -- 原理:将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务,并通过应用程序来总控各个小事务。 -- 优点:性能上有优势 -- 缺点:需要应用程序在事务控制上做灵活设计。如果使用了 spring 的事务管理,改动起来会面临一定的困难。 - -#### 跨节点 Join - -只要是进行切分,跨节点 Join 的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id,根据这些 id 发起第二次请求得到关联数据。 - -#### 跨节点的 count,order by,group by 以及聚合函数 - -这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。 - -解决方案:与解决跨节点 join 问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个节点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。 - -业务角度上的解决方案: - -- 如果是在前台应用提供分页,则限定用户只能看前面 n 页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)。 -- 如果是后台批处理任务要求分批获取数据,则可以加大 page size,比如每次获取 5000 条记录,有效减少分页数(当然离线访问一般走备库,避免冲击主库)。 -- 分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。 - -#### 分布式 ID - -一旦数据库被切分到多个物理节点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的 ID 无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得 ID,以便进行 SQL 路由。 - -一些常见的主键生成策略: - -- 使用全局唯一 ID:GUID。 -- 为每个分片指定一个 ID 范围。 -- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)。 - -#### 数据迁移,容量规划,扩容等问题 - -来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了 Sharding 扩容的难度。 - -## 集群 - -> 这个专题需要根据熟悉哪个数据库而定,但是主流、成熟的数据库都会实现一些基本功能,只是实现方式、策略上有所差异。由于本人较为熟悉 Mysql,所以下面主要介绍 Mysql 系统架构问题。 - -### 复制机制 - -Mysql 支持两种复制:基于行的复制和基于语句的复制。 - -这两种方式都是在主库上记录二进制日志(binlog),然后在从库上以异步方式更新主库上的日志记录。这意味着:复制过程存在时延,这段时间内,主从数据可能不一致(即最终一致性)。 - -主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。 - -- **binlog 线程** :负责将主服务器上的数据更改写入二进制文件(binlog)中。 -- **I/O 线程** :负责从主服务器上读取二进制日志文件,并写入从服务器的日志中。 -- **SQL 线程** :负责读取日志并执行 SQL 语句以更新数据。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave.png) - -### 读写分离 - -主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 - -读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。 - -MySQL 读写分离能提高性能的原因在于: - -- 主从服务器负责各自的读和写,极大程度缓解了锁的争用; -- 从服务器可以配置 `MyISAM` 引擎,提升查询性能以及节约系统开销; -- 增加冗余,提高可用性。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave-proxy.png) - -## 数据库优化 - -数据库优化的路线一般为:SQL 优化、结构优化、配置优化、硬件优化。前两个方向一般是普通开发的考量点,而后两个方向一般是 DBA 的考量点。 - -### SQL 优化 - -> SQL 优化是数据库优化的最常见、最初级手段。 -> -> 在执行 SQL 语句,语句中字段的顺序、查询策略等都可能会影响到 SQL 的执行性能。 - -#### 执行计划 - -如何检验修改后的 SQL 确实有优化效果?这就需要用到执行计划(`EXPLAIN`)。 - -使用执行计划 `EXPLAIN` 用来分析 `SELECT` 查询效率,开发人员可以通过分析 `EXPLAIN` 结果来优化查询语句。 - -比较重要的字段有: - -- `select_type` - 查询类型,有简单查询、联合查询、子查询等 -- `key` - 使用的索引 -- `rows` - 扫描的行数 - -> 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) - -#### 访问数据优化 - -减少请求的数据量: - -- **只返回必要的列** - 不要查询不需要的列,尽量避免使用 `SELECT *` 语句。 -- **只返回必要的行** - 使用 `WHERE` 语句进行查询过滤,有时候也需要使用 `LIMIT` 语句来限制返回的数据。 -- **缓存重复查询的数据** - 使用缓存可以避免在数据库中进行查询,特别要查询的数据经常被重复查询,缓存可以带来的查询性能提升将会是非常明显的。 - -减少服务器端扫描的行数: - -- 最有效的方式是**使用索引来覆盖查询**(即 `WHERE` 后的过滤查询字段最好是索引字段)。 - -#### 重构查询方式 - -##### 切分查询 - -一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 - -```sql -DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH); -``` - -```sql -rows_affected = 0 -do { - rows_affected = do_query( - "DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000") -} while rows_affected > 0 -``` - -##### 分解关联查询 - -将一个大连接查询(JOIN)分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有: - -- **缓存更高效**。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 -- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而**减少冗余记录的查询**。 -- **减少锁竞争**; -- **在应用层进行连接,可以更容易对数据库进行拆分**,从而更容易做到高性能和可扩展。 -- **查询本身效率也可能会有所提升**。例如下面的例子中,使用 `IN()` 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。 - -```sql -SELECT * FROM tag -JOIN tag_post ON tag_post.tag_id=tag.id -JOIN post ON tag_post.post_id=post.id -WHERE tag.tag='mysql'; -SELECT * FROM tag WHERE tag='mysql'; -SELECT * FROM tag_post WHERE tag_id=1234; -SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); -``` - -#### SQL 语句细节 - -##### 选择最有效率的表名顺序 - -数据库按照**从右到左的顺序处理 FROM 子句中的表名,FROM 子句中写在最后的表将被最先处理**。 - -在 `FROM` 子句中包含多个表的情况下: - -- 如果多个表是完全**无关系**的话,将记录和列名最少的表,写在最后,然后依次类推。也就是说:**选择记录条数最少的表放在最后**。 - -如果有 3 个以上的表连接查询: - -- 如果多个表是**有关系**的话,将引用最多的表,放在最后,然后依次类推。也就是说:**被其他表所引用的表放在最后**。 - -例如:查询员工的编号,姓名,工资,工资等级,部门名 - -**emp 表被引用得最多,记录数也是最多,因此放在 form 字句的最后面** - -```sql -select emp.empno,emp.ename,emp.sal,salgrade.grade,dept.dname -from salgrade,dept,emp -where (emp.deptno = dept.deptno) and (emp.sal between salgrade.losal and salgrade.hisal) -``` - -##### WHERE 子句中的连接顺序 - -数据库按照**从右到左的顺序解析 `WHERE` 子句**。 - -因此,**表之间的连接必须写在其他 WHERE 条件的左边**,那些**可以过滤掉最大数量记录的条件必须写在 WHERE 子句的之右**。 - -**emp.sal 可以过滤多条记录,写在 WHERE 字句的最右边** - -```sql -select emp.empno,emp.ename,emp.sal,dept.dname -from dept,emp -where (emp.deptno = dept.deptno) and (emp.sal > 1500) -``` - -##### SELECT 子句中避免使用 `*` 号 - -我们当时学习的时候,“\*” 号是可以获取表中全部的字段数据的。 - -- **但是它要通过查询数据字典完成的,这意味着将耗费更多的时间** -- 使用\*号写出来的 SQL 语句也不够直观。 - ---- - -##### 用 TRUNCATE 替代 DELETE - -如果需要**清空所有表记录**,使用 TRUNCATE 比 DELETE 执行效率高: - -**DELETE 是一条一条记录的删除,而 Truncate 是将整个表删除,仅保留表结构** - -##### 使用内部函数提高 SQL 效率 - -**例如使用 mysql 的 concat() 函数会比使用 `||` 拼接速度快,因为 concat() 函数已经被 mysql 优化过了。** - -##### 使用表或列的别名 - -如果表或列的名称太长了,使用一些简短的别名也能稍微提高一些 SQL 的性能。毕竟要扫描的字符长度就变少了。 - -##### SQL 关键字大写 - -我们在编写 SQL 的时候,官方推荐的是使用大写来写关键字,**因为 Oracle 服务器总是先将小写字母转成大写后,才执行** - -##### 用 `>=` 替代 `>` - -❌ 低效方式: - -```sql --- 首先定位到DEPTNO=3的记录并且扫描到第一个DEPT大于3的记录 -SELECT * FROM EMP WHERE DEPTNO > 3 -``` - -✔ 高效方式: - -```sql --- 直接跳到第一个DEPT等于4的记录 -SELECT * FROM EMP WHERE DEPTNO >= 4 -``` - -##### 用 IN 替代 OR - -❌ 低效方式: - -```sql -select * from emp where sal = 1500 or sal = 3000 or sal = 800; -``` - -✔ 高效方式: - -```sql -select * from emp where sal in (1500,3000,800); -``` - -##### 总是使用索引的第一个列 - -如果索引是建立在多个列上,只有在它的第一个列被 WHERE 子句引用时,优化器才会选择使用该索引。 当只引用索引的第二个列时,不引用索引的第一个列时,优化器使用了全表扫描而忽略了索引 - -```sql -create index emp_sal_job_idex -on emp(sal,job); ----------------------------------- -select * -from emp -where job != 'SALES'; -``` - -##### SQL 关键字尽量大写 - -SQL 关键字尽量大写,如:Oracle 默认会将 SQL 语句中的关键字转为大写后在执行。 - -### 结构优化 - -数据库结构优化可以从以下方向着手: - -- 数据类型优化 -- 范式和反范式优化 -- 索引优化 - 细节请看索引和约束章节 -- 分库分表 - 细节请看分库分表章节 - -#### 数据类型优化原则 - -- 更小的通常更好 -- 简单就好,如整型比字符型操作代价低 -- 尽量避免 NULL - -#### 范式和反范式 - -范式和反范式各有利弊,需要根据实际情况权衡。 - -范式化的目标是**尽力减少冗余列,节省空间**。 - -- 范式化的优点是: - - - 减少冗余列,要写的数据就少,写操作的性能提高; - - 检索列数据时,`DISTINCT` 或 `GROUP BY` 操作减少。 - -- 范式化的缺点是:增加关联查询。 - -反范式化的目标是**适当增加冗余列,以避免关联查询**。 - -反范式化的缺点是: - -- 冗余列增多,空间变大,写操作性能下降; -- 检索列数据时,DISTINCT 或 GROUP BY 操作变多; - -### 配置优化 - -> 配置优化主要是针对 Mysql 服务器,例如:`max_connections`、`max_heap_table_size`、`open_files_limit`、`max_allowed_packet` 等等。 -> -> 在不同环境,不同场景下,应该酌情使用合理的配置。这种优化比较考验 Mysql 运维经验,一般是 DBA 的考量,普通开发接触的较少。 -> -> Mysql 配置说明请参考:[Mysql 服务器配置说明](sql/mysql/mysql-config.md) - -### 硬件优化 - -数据库扩容、使用高配设备等等。核心就是一个字:钱。 - -## 数据库理论 - -### 函数依赖 - -记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。 - -如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。 - -对于 A->B,如果能找到 A 的真子集 A',使得 A'-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖; - -对于 A->B,B->C,则 A->C 是一个传递依赖。 - -### 异常 - -以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade,键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。 - -| Sno | Sname | Sdept | Mname | Cname | Grade | -| :-: | :----: | :----: | :----: | :----: | :---: | -| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 | -| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 | -| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 | -| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 | - -不符合范式的关系,会产生很多异常,主要有以下四种异常: - -- 冗余数据:例如 学生-2 出现了两次。 -- 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。 -- 删除异常:删除一个信息,那么也会丢失其它信息。例如如果删除了 课程-1,需要删除第一行和第三行,那么 学生-1 的信息就会丢失。 -- 插入异常,例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。 - -### 范式 - -范式理论是为了解决以上提到四种异常。 - -高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。 - -
- -
- -#### 第一范式 (1NF) - -属性不可分。 - -#### 第二范式 (2NF) - -- 每个非主属性完全函数依赖于键码。 - -- 可以通过分解来满足。 - -**分解前** - -| Sno | Sname | Sdept | Mname | Cname | Grade | -| :-: | :----: | :----: | :----: | :----: | :---: | -| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 | -| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 | -| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 | -| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 | - -以上学生课程关系中,{Sno, Cname} 为键码,有如下函数依赖: - -- Sno -> Sname, Sdept -- Sdept -> Mname -- Sno, Cname-> Grade - -Grade 完全函数依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。 - -Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。 - -**分解后** - -关系-1 - -| Sno | Sname | Sdept | Mname | -| :-: | :----: | :----: | :----: | -| 1 | 学生-1 | 学院-1 | 院长-1 | -| 2 | 学生-2 | 学院-2 | 院长-2 | -| 3 | 学生-3 | 学院-2 | 院长-2 | - -有以下函数依赖: - -- Sno -> Sname, Sdept, Mname -- Sdept -> Mname - -关系-2 - -| Sno | Cname | Grade | -| :-: | :----: | :---: | -| 1 | 课程-1 | 90 | -| 2 | 课程-2 | 80 | -| 2 | 课程-1 | 100 | -| 3 | 课程-2 | 95 | - -有以下函数依赖: - -- Sno, Cname -> Grade - -#### 第三范式 (3NF) - -- 非主属性不传递依赖于键码。 - -上面的 关系-1 中存在以下传递依赖:Sno -> Sdept -> Mname,可以进行以下分解: - -关系-11 - -| Sno | Sname | Sdept | -| :-: | :----: | :----: | -| 1 | 学生-1 | 学院-1 | -| 2 | 学生-2 | 学院-2 | -| 3 | 学生-3 | 学院-2 | - -关系-12 - -| Sdept | Mname | -| :----: | :----: | -| 学院-1 | 院长-1 | -| 学院-2 | 院长-2 | - -## 存储引擎 - -Mysql 有多种存储引擎,**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的**。 - -简单列举几个存储引擎: - -- **InnoDB** - Mysql 的默认事务型存储引擎,并提供了行级锁和外键的约束。性能不错且支持自动故障恢复。 -- **MyISAM** - Mysql 5.1 版本前的默认存储引擎。特性丰富但不支持事务,也不支持行级锁和外键,也没有故障恢复功能。 -- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。 -- **MEMORY** 。所有的数据都在内存中,数据的处理速度快,但是安全性不高。 - -### InnoDB vs. MyISAM - -InnoDB 和 MyISAM 是目前使用的最多的两种 Mysql 存储引擎。 - -- 数据结构比较: - - InnoDB 和 MyISAM 的索引数据结构**都是 B+ 树**。 - - MyIASM 的 B+ 树中存储的内容实际上是实际数据的地址值。也就是说它的索引和实际数据是分开的,**只不过使用索引指向了实际数据。这种索引的模式被称为非聚集索引。** - - InnoDB 的 B+ 树中存储的内容是实际的数据,这种索引有被称为聚集索引。 -- 事务支持比较: - - InnoDB 支持事务,并提供了行级锁和外键的约束。 - - MyIASM 不支持事务,也不支持行级锁和外键。 -- 故障恢复比较: - - InnoDB 支持故障恢复。 - - MyISAM 不支持故障恢复。 - -## 数据库比较 - -### 常见数据库比较 - -- `Oracle` - 久负盛名的商业数据库。功能强大、稳定。最大的缺点就是费钱。 -- `Mysql` - 曾经是互联网公司的最爱,但自动 Mysql 被 Oracle 公司收购后,好日子可能一去不复返。很多公司或开源项目已经逐渐寻找其他的开源产品来替代 Mysql。 -- `MariaDB` - 开源关系型数据库。 MySQL 的真正开源的发行版本,由 Mysql 部分核心人员创建。可作为 Mysql 的替代产品。 -- `PostgreSQL` - 开源关系型数据库。和 MySQL 的工作方式非常相似,社区支持做得很好。可作为 Mysql 的替代产品。 -- `SQLite` - 开源的轻量级数据库,移动端常常使用。 -- `H2` - 内存数据库,一般用作开发、测试环境数据库。 -- `SQL Server` - 微软 Windows 生态系统的数据库。我想,Java 程序员应该没人用吧。 - -### Oracle vs. Mysql - -目前为止,Java 领域用的最多的关系型数据库,应该还是 Oracle 和 Mysql,所以这里做一下比较。 - -#### 数据库对象差异 - -在 Mysql 中,**一个用户可以创建多个库**。 - -而在 Oracle 中,Oracle 服务器是由两部分组成 - -- 数据库实例【理解为对象,看不见的】 -- 数据库【理解为类,看得见的】 - -**一个数据库实例可拥有多个用户,一个用户默认拥有一个表空间。** - -**表空间是存储我们数据库表的地方,表空间内可以有多个文件。** - -#### SQL 差异 - -(1)主键递增 - -Mysql 可以设置 `AUTO_INCREMENT` 约束来指定主键为自增序列。 - -Oracle 需要通过 `CREATE SEQUENCE` 创建序列。 - -(2)分页查询 - -Mysql 分页基于 `SELECT ... FROM ... LIMIT ...` 完成,较简单。 - -```sql -select * from help_category order by parent_category_id limit 10,5; -``` - -Oracle 分页基于 `SELECT ... FROM (SELECT ROWNUM ...) WHERE ...` 完成,较复杂。 - -```sql -select * from -(select rownum rr,a.* from (select * from emp order by sal) a ) -where rr>5 and rr<=10; -``` - -#### 事务差异 - -- auto commit - - Mysql 事务是 autocommit 模式,即自动提交事务; - - Oracle 事务需要手动 `COMMIT`。 -- 事务隔离级别 - - Mysql 默认的事务隔离级别是可重复读(`REPEATABLE READ`) - - Oracle 支持读已提交(`READ COMMITTED`)和串行化(`SERIALIZABLE`) 两种事务隔离级别,默认事务隔离级别是读已提交(`READ COMMITTED`) - -### 数据类型比较 - -> 不同数据库中,对数据类型的支持是不一样的。 -> -> 即使存在同一种数据类型,也可能存在名称不同、或大小不同等问题。 -> -> 因此,对于数据类型的支持详情必须参考各数据库的官方文档。 - -下面列举一些常见数据类型对比: - -| 数据类型 | Oracle | MySQL | PostgreSQL | -| :------------------ | :--------------- | :---------- | :--------------- | -| `boolean` | Byte | N/A | Boolean | -| `integer` | Number | Int Integer | Int Integer | -| `float` | Number | Float | Numeric | -| `currency` | N/A | N/A | Money | -| `string (fixed)` | Char | Char | Char | -| `string (variable)` | Varchar Varchar2 | Varchar | Varchar | -| `binary object` | Long Raw | Blob Text | Binary Varbinary | - -> 数据类型对比表摘自 [SQL 通用数据类型](https://www.runoob.com/sql/sql-datatypes-general.html)、[SQL 用于各种数据库的数据类型](https://www.runoob.com/sql/sql-datatypes.html) - -## SQL FAQ - -### SELECT COUNT(\*)、SELECT COUNT(1) 和 SELECT COUNT(具体字段) 性能有差别吗? - -在 MySQL InnoDB 存储引擎中,`COUNT(*)` 和 `COUNT(1)` 都是对所有结果进行 `COUNT`。因此`COUNT(*)`和`COUNT(1)`本质上并没有区别,执行的复杂度都是 `O(N)`,也就是采用全表扫描,进行循环 + 计数的方式进行统计。 - -如果是 MySQL MyISAM 存储引擎,统计数据表的行数只需要`O(1)`的复杂度,这是因为每张 MyISAM 的数据表都有一个 meta 信息存储了`row_count`值,而一致性则由表级锁来保证。因为 InnoDB 支持事务,采用行级锁和 MVCC 机制,所以无法像 MyISAM 一样,只维护一个`row_count`变量,因此需要采用扫描全表,进行循环 + 计数的方式来完成统计。 - -需要注意的是,在实际执行中,`COUNT(*)`和`COUNT(1)`的执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的。 - -另外在 InnoDB 引擎中,如果采用`COUNT(*)`和`COUNT(1)`来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于`COUNT(*)`和`COUNT(1)`来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。 - -然而如果想要查找具体的行,那么采用主键索引的效率更高。如果有多个二级索引,会使用 key_len 小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。 - -这里我总结一下: - -1. 一般情况下,三者执行的效率为 `COUNT(*)`= `COUNT(1)`> `COUNT(字段)`。我们尽量使用`COUNT(*)`,当然如果你要统计的是某个字段的非空数据行数,则另当别论,毕竟比较执行效率的前提是结果一样才可以。 -2. 如果要统计`COUNT(*)`,尽量在数据表上建立二级索引,系统会自动采用`key_len`小的二级索引进行扫描,这样当我们使用`SELECT COUNT(*)`的时候效率就会提升,有时候可以提升几倍甚至更高。 - -> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) - -### ORDER BY 是对分的组排序还是对分组中的记录排序呢? - -ORDER BY 就是对记录进行排序。如果你在 ORDER BY 前面用到了 GROUP BY,实际上这是一种分组的聚合方式,已经把一组的数据聚合成为了一条记录,再进行排序的时候,相当于对分的组进行了排序。 - -### SELECT 语句内部的执行步骤 - -一条完整的 SELECT 语句内部的执行顺序是这样的: - -1. FROM 子句组装数据(包括通过 ON 进行连接); -2. WHERE 子句进行条件筛选; -3. GROUP BY 分组 ; -4. 使用聚集函数进行计算; -5. HAVING 筛选分组; -6. 计算所有的表达式; -7. SELECT 的字段; -8. ORDER BY 排序; -9. LIMIT 筛选。 - -> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) - -### 解哪种情况下应该使用 EXISTS,哪种情况应该用 IN - -索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。 - -比如下面这样: - -``` - SELECT * FROM A WHERE cc IN (SELECT cc FROM B) - SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc=A.cc) -``` - -当 A 小于 B 时,用 EXISTS。因为 EXISTS 的实现,相当于外表循环,实现的逻辑类似于: - -``` - for i in A - for j in B - if j.cc == i.cc then ... -``` - -当 B 小于 A 时用 IN,因为实现的逻辑类似于: - -``` - for i in B - for j in A - if j.cc == i.cc then ... -``` - -哪个表小就用哪个表来驱动,A 表小就用 EXISTS,B 表小就用 IN。 - -> ——摘自[极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) - -## 参考资料 - -- [数据库面试题(开发者必看)](https://juejin.im/post/5a9ca0d6518825555c1d1acd) -- [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md) -- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) -- [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c) -- [数据库分库分表(sharding)系列(二) 全局主键生成策略](https://blog.csdn.net/bluishglc/article/details/7710738) -- [一种支持自由规划无须数据迁移和修改路由代码的 Sharding 扩容方案](https://blog.csdn.net/bluishglc/article/details/7970268) -- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) -- [mysql 和 oracle 的区别](https://zhuanlan.zhihu.com/p/39651803) -- [RUNOOB SQL 教程](https://www.runoob.com/sql/sql-tutorial.html) -- [如果有人问你数据库的原理,叫他看这篇文章](https://gameinstitute.qq.com/community/detail/107154) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\346\246\202\350\256\272.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\346\246\202\350\256\272.md" new file mode 100644 index 0000000000..007aaae4e2 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/01.\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\346\246\202\350\256\272.md" @@ -0,0 +1,244 @@ +--- +title: 数据库系统概论 +date: 2023-11-01 09:39:59 +order: 01 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 +permalink: /pages/e1121b/ +--- + +# 数据库系统概论 + +## 数据库核心术语 + +### 数据 + +数据**是数据库中存储的基本对象**,可以对数据做如下定义:**描述事物的符号称为数据**。 + +描述事物的符号多种多样,所以数据有多种表现形式。 + +数据的表现形式还不能完全地表达其内容,需要经过解释,数据和关于数据的解释是密不可分的,每一个数据都有它的意义,数据的解释指的是对数据含义的说明,**数据的含义称为数据的语义,数据与其语义是不可分的。** + +### 数据库 + +数据库就是存放数据的仓库,只不过这个仓库是在计算机存储设备上,而且数据是按一定格式存放的。 + +严格的来讲,数据库的含义如下: + +- 长期存储在计算机内,有组织的,可共享大量数据的集合 +- 里面的数据按照一定的数据模型组织,描述和储存 +- 具有较小的冗余度,较高的数据独立性和易扩展性,并可为各种用户共享 + +数据库特点: + +- 永久存储 +- 有组织 +- 可共享 + +### 数据库管理系统 + +数据库管理系统(Database Management System, DBMS)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。 用户通过 DBMS 访问数据库中的数据,数据库管理员也通过 DBMS 进行数据库的维护工作。 + +数据库管理系统的功能主要包括以下方面: + +- 数据定义功能 +- 数据组织,存储和管理 +- 数据操纵功能 +- 数据库的事务管理和运行管理 +- 数据库的建立和维护功能 + - 数据库初始数据输入,转换功能 + - 数据库的转储,恢复功能 + - 数据库的重组织功能 + - 数据库的性能监视,分析功能 +- 其他功能 + - 数据库管理系统与网络中其他软件系统的通信功能 + - 一个数据库管理系统与另一个数据库管理系统或文件系统的数据转换功能 + - 异构数据库之间的互访和互操作功能等 + +### 数据库系统 + +数据库系统是由**数据库,数据库管理系统,应用程序和数据库管理员组成的存储,管理,处理和维护数据的系统**。 + +### OLTP 和 OLAP + +OLTP 和 OLAP 的共性: + +OLTP 和 OLAP 都是用于存储和处理大量数据的数据库管理系统。它们都需要高效可靠的 IT 基础设施才能平稳运行。可以同时使用它们来查询现有数据或存储新数据。两者都支持组织中数据驱动的决策。大多数公司同时使用 OLTP 和 OLAP 系统来满足其商业智能需求。 + +OLTP 和 OLAP 的区别: + +**“联机事务处理 (OLTP) ”系统的主要用途是处理数据库事务**。 + +**“联机分析处理 (OLAP) ”系统的主要用途是分析聚合数据**。 + +## 数据模型 + +数据模型是**对现实世界数据特征的抽象**,也就是说**数据模型是用来描述数据,组织数据和对数据进行操作的**。 + +现有的数据库都是基于某种数据模型的,**数据模型是数据库系统的核心和基础**。 + +数据模型可以分为两类:第一类是概念模型,第二类是逻辑模型和物理模型。 + +### 概念模型 + +**概念模型**也称为信息模型,它是按照用户的观点来对数据和信息建模,主要用于数据库设计。 + +概念模型主要涉及以下一些概念: + +- 实体 - 客观存在并可相互区别的事务称为实体。 +- 属性 - 实体具有的某一特性称为属性。 +- 码 - 唯一标识实体的属性集称为码。 +- 实体模型 - 用实体名及其属性名集合来抽象和刻画同类实体,称为实体模型。 +- 实体集 - 同一类型实体的集合就是实体集。 +- 联系 + - 实体之间的联系通常指的是不同实体集之间的联系。 + - 实体之间的联系有一对一,一对多和多对多等多种类型。 + +概念模型的一种表示方法是:**实体-联系方法**,该方法使用 ER 图来描述现实世界的概念模型,ER 方法也成为 ER 模型。 + +### 逻辑模型 + +**逻辑模型**是计算机系统的观点对数据建模,主要用于数据库管理系统的实现。 + +逻辑模型通常由三部分组成: + +- **数据结构** - 数据结构描述数据库的组成对象以及对象之间的联系,也就是说,数据结构描述的内容有两类,一类是对象的类型、内容、性质有关的,一类是与数据之间联系有关的对象。 +- **数据操作** - 数据操作指的是对数据库中各种对象的实例允许执行的操作的集合,包括操作及有关的操作规则。主要有查询和更新两大操作。 +- **数据的完整性约束** - 数据的完整性约束条件是一组完整性规则。 + +**物理模型**是对数据最底层的抽象,他描述数据在系统内部的表示和存储方式,是面向计算机系统的。 + +## 函数依赖 + +**定义**:设 **R(U)** 是**属性集合 U={ A1, A2, ... , An }** 上的**一个关系模式**,X, Y 是 U 上的两个子集,若对 R(U) 的任意一个可能的关系 r ,r 中不可能有两个元组满足在 X 中的属性值相等而在 Y 中的属性值不等,则称 “ X 函数决定 Y ” 或 “ Y 函数依赖于 X ” ,记作 X->Y 。 + +**白话**:在一个**关系 R** 中,属性(组) Y 的值是由属性(组) X 的值所决定的 。又可以说,在关系 R 中,**若两个元组的 X 属性值相同,那么这两个元组的 Y 属性值也相同**。 + +为什么叫做**函数**依赖? 函数的定义:对于定义域中任意 x ,有且只有一个 y 与之对应。 属性之间的依赖:对于相同的 X 属性值,有且只有一个 Y 属性值与之对应。 + +**本质**:函数依赖的本质就是反应了 一个关系中**属性之间**的**约束关系**,或者**依赖关系**。函数依赖是一种数据依赖。 + +**举例**: + +``` +U = { 学号,姓名,年龄,专业 } +{学号} → { 姓名,年龄,专业 } +``` + +> 扩展阅读:[白话详解数据库函数依赖和 Armstrong 公理及其引理](https://zhuanlan.zhihu.com/p/344087914) + +## 范式 + +数据库规范化,又称“**范式**”,是数据库设计的指导理论。**范式的目标是:使数据库结构更合理,消除存储异常,使数据冗余尽量小,增进数据的一致性**。 + +根据约束程度从低到高有:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)等等。现代数据库设计,一般最多满足 3NF——范式过高,虽然具有对数据关系更好的约束性,但也导致数据关系表增加而令数据库 IO 更繁忙。因此,在实际应用中,本来可以交由数据库处理的关系约束,很多都是在数据库使用程序中完成的。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030715177.png) + +### 第一范式 (1NF) + +1NF 要求属性具有原子性,不可再分解。 + +### 第二范式 (2NF) + +2NF 要求记录有唯一标识,即实体的唯一性,即**不存在部分依赖**。 + +假设有一张 student 表,结构如下: + +``` +-- 学生表 +student(学号、课程号、姓名、学分、成绩) +``` + +举例来说,现有一张 student 表,具有学号、课程号、姓名、学分等字段。从中可以看出,表中包含了学生信息和课程信息。由于非主键字段必须依赖主键,这里学分依赖课程号,姓名依赖学号,所以不符合 2NF。 + +不符合 2NF 可能会存在的问题: + +- **数据冗余** - 每条记录都含有相同信息。 +- **删除异常** - 删除所有学生成绩,就把课程信息全删除了。 +- **插入异常** - 学生未选课,无法记录进数据库。 +- **更新异常** - 调整课程学分,所有行都调整。 + +根据 2NF 可以拆分如下: + +``` +-- 学生表 +student(学号、姓名) +-- 课程表 +course(课程号、学分) +-- 学生课程关系表 +student_course(学号、课程号、成绩) +``` + +### 第三范式 (3NF) + +**如果一个关系属于第二范式**,并且在**两个(或多个)非主键属性之间不存在函数依赖**(非主键属性之间的函数依赖也称为传递依赖),那么这个关系属于第三范式。 + +3NF 是对字段的**冗余性**,要求任何字段不能由其他字段派生出来,它要求字段没有冗余,即**不存在传递依赖**。 + +假设有一张 student 表,结构如下: + +``` +-- 学生表 +student(学号、姓名、年龄、班级号、班主任) +``` + +上表属于第二范式,因为主键由单个属性组成(学号)。 + +因为存在**依赖传递**:(学号) → (学生)→(所在班级) → (班主任) 。 + +**可能会存在问题:** + +- 数据冗余 - 有重复值; +- 更新异常 - 有重复的冗余信息,修改时需要同时修改多条记录,否则会出现**数据不一致的情况** 。 + +可以基于 3NF 拆解: + +``` +student(学号、姓名、年龄、所在班级号) +class(班级号、班主任) +``` + +### 反范式 + +前文已提及,**范式的目标是:使数据库结构更合理,消除存储异常,使数据冗余尽量小,增进数据的一致性**。反范式,顾名思义,与范式正好相反。 + +是不是范式越严格越好呢(也就是意味着极力避免冗余,甚至消除冗余)?这也不是一定的,范式越高意味着表的划分更细,一个数据库中需要的表也就越多,用户不得不将原本相关联的数据分摊到多个表中。当用户同时需要这些数据时只能通过关联表的形式将数据重新合并在一起。同时把多个表联接在一起的花费是巨大的,尤其是当需要连接的两张或者多张表数据非常庞大的时候,表连接操作几乎是一个噩梦,这严重地降低了系统运行性能。因此,**有时为了提高查询效率,有必要适当的冗余数据,以达到空间换时间的目的——这就是“反范式”**。 + +## ER 图 + +E-R 图又称实体关系图,是一种提供了实体,属性和联系的方法,用来描述现实世界的概念模型。通俗点讲就是,当我们理解了实际问题的需求之后,需要用一种方法来表示这种需求,概念模型就是用来描述这种需求。 + +ER 图中的要素: + +- **实体** - **用矩形表示**。实际问题中客观存在的并且可以相互区别的事物称为实体。实体是现实世界中的对象,可以具体到人,事,物。可以是学生,教师,图书馆的书籍。 +- **属性** - **用椭圆形表示**。实体所具有的某一个特性称为属性,在 E-R 图中属性用来描述实体。比如:可以用“姓名”“姓名”“出生日期”来描述人。 +- **主键** - **在属性下方标记下划线**。在描述实体集的所有属性中,可以唯一标识每个实体的属性称为键。键也是属于实体的属性,作为键的属性取值必须唯一且不能“空置”。 +- **联系** - **用菱形表示**。世界上任何事物都不是孤立存在的,事物内部和事物之间都有联系的,实体之间的联系通常有 3 种类型:一对一联系,一对多联系,多对多联系。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030715877.png) + +绘制 ER 图常用软件: + +- [drawio 官网](https://www.draw.io/) - 开源的绘图工具,特点是简洁、清晰,并且同时支持线上线下绘图。 +- [Visio](https://products.office.com/zh-cn/visio/flowchart-software) - Office 的绘图工具,特点是简单、清晰。 +- [亿图](http://www.edrawsoft.cn/) - 国内开发的、收费的绘图工具。图形模板、素材非常全面,样式也很精美,可以导出为 word、pdf、图片。 +- [StarUML](http://staruml.io/) - 样式精美,功能全面的 UML 工具。 +- [Astah 官网](http://astah.net/) - 样式不错,功能全面的绘图工具。 +- [ArgoUML 官网](https://argouml.en.softonic.com/?ex=CAT-759.2) +- [ProcessOn 官网](https://www.processon.com/) - 在线绘图工具,特点是简洁、清晰。 + +> 扩展阅读: +> +> [ER 图(实体关系图)怎么画?](https://www.zhihu.com/tardis/zm/art/270299029?source_id=1003) + +## 参考资料 + +- [《数据库系统概论(第 5 版)》](https://book.douban.com/subject/26317662/) +- [数据库逻辑设计之三大范式通俗理解,一看就懂,书上说的太晦涩](https://segmentfault.com/a/1190000013695030) +- [ER 图(实体关系图)怎么画?](https://www.zhihu.com/tardis/zm/art/270299029?source_id=1003) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225.md" new file mode 100644 index 0000000000..7c2412634c --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225.md" @@ -0,0 +1,1188 @@ +--- +title: SQL 语法速成 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202310011053288.png +date: 2018-06-15 16:07:17 +order: 02 +categories: + - 数据库 + - 关系型数据库 + - 综合 +tags: + - 数据库 + - 关系型数据库 + - SQL +permalink: /pages/b71c9e/ +--- + +# SQL 语法速成 + +> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 +> +> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310011053288.png) + +## SQL 简介 + +### 数据库术语 + +- **数据库(database)** - 保存有组织的数据的容器(通常是一个文件或一组文件)。 +- **数据表(table)** - 某种特定类型数据的结构化清单。 +- **模式(schema)** - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。 +- **行(row)** - 表中的一条记录。 +- **列(column)** - 表中的一个字段。所有表都是由一个或多个列组成的。 +- **主键(primary key)** - 一列(或一组列),其值能够唯一标识表中每一行。 + +### SQL 语法 + +> SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 + +#### SQL 语法结构 + +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/sql-syntax.png) + +SQL 语法结构包括: + +- **子句** - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。) +- **表达式** - 可以产生任何标量值,或由列和行的数据库表 +- **谓词** - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。 +- **查询** - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。 +- **语句** - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。 + +#### SQL 语法要点 + +- **SQL 语句不区分大小写**,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。 + +例如:`SELECT` 与 `select` 、`Select` 是相同的。 + +- **多条 SQL 语句必须以分号(`;`)分隔**。 + +- 处理 SQL 语句时,**所有空格都被忽略**。SQL 语句可以写成一行,也可以分写为多行。 + +```sql +-- 一行 SQL 语句 +UPDATE user SET username='robot', password='robot' WHERE username = 'root'; + +-- 多行 SQL 语句 +UPDATE user +SET username='robot', password='robot' +WHERE username = 'root'; +``` + +- SQL 支持三种注释 + +```sql +## 注释1 +-- 注释2 +/* 注释3 */ +``` + +#### SQL 分类 + +##### DDL + +**DDL**,英文叫做 Data Definition Language,即**“数据定义语言”**。**DDL 用于定义数据库对象**。 + +DDL 定义操作包括创建(`CREATE`)、删除(`DROP`)、修改(`ALTER`);而被操作的对象包括:数据库、数据表和列、视图、索引。 + +##### DML + +**DML**,英文叫做 Data Manipulation Language,即**“数据操作语言”**。**DML 用于访问数据库的数据**。 + +DML 访问操作包括插入(`INSERT`)、删除(`DELETE`)、修改(`UPDATE`)、查询(`SELECT`)。这四个指令合称 **CRUD**,英文单词为 Create, Read, Update, Delete,即增删改查。 + +##### TCL + +**TCL**,英文叫做 Transaction Control Language,即**“事务控制语言”**。**TCL 用于管理数据库中的事务**,实际上就是用于管理由 DML 语句所产生的数据变更,它还允许将语句分组为逻辑事务。 + +TCL 的核心指令是 `COMMIT`、`ROLLBACK`。 + +##### DCL + +**DCL**,英文叫做 Data Control Language,即**“数据控制语言”**。DCL 用于对数据访问权进行控制,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。 + +DCL 的核心指令是 `GRANT`、`REVOKE`。 + +DCL 以**控制用户的访问权限**为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:`CONNECT`、`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`EXECUTE`、`USAGE`、`REFERENCES`。根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。 + +## 数据定义(CREATE、ALTER、DROP) + +> DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)。 + +### 数据库(DATABASE) + +#### 创建数据库 + +```sql +CREATE DATABASE IF NOT EXISTS db_tutorial; +``` + +#### 删除数据库 + +```sql +DROP DATABASE IF EXISTS db_tutorial; +``` + +#### 选择数据库 + +```sql +USE db_tutorial; +``` + +### 数据表(TABLE) + +#### 创建数据表 + +**普通创建** + +```sql +CREATE TABLE user ( + id INT(10) UNSIGNED NOT NULL COMMENT 'Id', + username VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '用户名', + password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', + email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' +) COMMENT ='用户表'; +``` + +**根据已有的表创建新表** + +```sql +CREATE TABLE vip_user AS +SELECT * FROM user; +``` + +#### 修改数据表 + +##### 添加列 + +```sql +ALTER TABLE user +ADD age int(3); +``` + +##### 删除列 + +```sql +ALTER TABLE user +DROP COLUMN age; +``` + +##### 修改列 + +```sql +ALTER TABLE `user` +MODIFY COLUMN age tinyint; +``` + +#### 删除数据表 + +```sql +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS vip_user; +``` + +### 视图(VIEW) + +**“视图”是基于 SQL 语句的结果集的可视化的表**。视图是虚拟的表,本身不存储数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。 + +视图的作用: + +- 简化复杂的 SQL 操作,比如复杂的连接。 +- 只使用实际表的一部分数据。 +- 通过只给用户访问视图的权限,保证数据的安全性。 +- 更改数据格式和表示。 + +#### 创建视图 + +```sql +CREATE VIEW top_10_user_view AS +SELECT id, username FROM user +WHERE id < 10; +``` + +#### 删除视图 + +```sql +DROP VIEW top_10_user_view; +``` + +### 索引(INDEX) + +**“索引”是数据库为了提高查找效率的一种数据结构**。 + +日常生活中,我们可以通过检索目录,来快速定位书本中的内容。索引和数据表,就好比目录和书,想要高效查询数据表,索引至关重要。在数据量小且负载较低时,不恰当的索引对于性能的影响可能还不明显;但随着数据量逐渐增大,性能则会急剧下降。因此,**设置合理的索引是数据库查询性能优化的最有效手段**。 + +更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。 + +“唯一索引”表明此索引的每一个索引值只对应唯一的数据记录。 + +#### 创建索引 + +```sql +CREATE INDEX idx_email + ON user(email); +``` + +#### 创建唯一索引 + +```sql +CREATE UNIQUE INDEX uniq_name + ON user(name); +``` + +#### 删除索引 + +```sql +ALTER TABLE user +DROP INDEX idx_email; +ALTER TABLE user +DROP INDEX uniq_name; +``` + +#### 添加主键 + +```sql +ALTER TABLE user +ADD PRIMARY KEY (id); +``` + +#### 删除主键 + +```sql +ALTER TABLE user +DROP PRIMARY KEY; +``` + +### 约束 + +> SQL 约束用于规定表中的数据规则。 + +如果存在违反约束的数据行为,行为会被约束终止。约束可以在创建表时规定(通过 `CREATE TABLE` 语句),或者在表创建之后规定(通过 `ALTER TABLE` 语句)。 + +约束类型 +- `NOT NULL` - 指示字段不能存储 `NULL` 值。 +- `UNIQUE` - 保证字段的每行必须有唯一的值。 +- `PRIMARY KEY` - PRIMARY KEY 的作用是唯一标识一条记录,不能重复,不能为空,即相当于 `NOT NULL` + `UNIQUE`。确保字段(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。 +- `FOREIGN KEY` - 保证一个表中的数据匹配另一个表中的值的参照完整性。 +- `CHECK` - 用于检查字段取值范围的有效性。 +- `DEFAULT` - 表明字段的默认值。如果插入数据时,该字段没有赋值,就会被设置为默认值。 + +创建表时使用约束条件: + +```sql +CREATE TABLE Users ( + Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id', + Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名', + Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', + Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址', + Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效', + PRIMARY KEY (Id) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +``` + +## 增删改查(CRUD) + +增删改查,又称为 **`CRUD`**,是数据库基本操作中的基本操作。 + +### 插入数据 + +> - `INSERT INTO` 语句用于向表中插入新记录。 + +#### 插入完整的行 + +```sql +INSERT INTO user +VALUES (10, 'root', 'root', 'xxxx@163.com'); +``` + +#### 插入行的一部分 + +```sql +INSERT INTO user(username, password, email) +VALUES ('admin', 'admin', 'xxxx@163.com'); +``` + +#### 插入查询出来的数据 + +```sql +INSERT INTO user(username) +SELECT name +FROM account; +``` + +### 更新数据 + +> - `UPDATE` 语句用于更新表中的记录。 + +```sql +UPDATE user +SET username='robot', password='robot' +WHERE username = 'root'; +``` + +### 删除数据 + +> - `DELETE` 语句用于删除表中的记录。 +> - `TRUNCATE TABLE` 可以清空表,也就是删除所有行。 + +#### 删除表中的指定数据 + +```sql +DELETE FROM user WHERE username = 'robot'; +``` + +#### 清空表中的数据 + +```sql +TRUNCATE TABLE user; +``` + +### 查询数据 + +> - `SELECT` 语句用于从数据库中查询数据。 +> - `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 +> - `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 +> - `ASC` :升序(默认) +> - `DESC` :降序 + +#### 查询单列 + +```sql +SELECT prod_name FROM products; +``` + +#### 查询多列 + +```sql +SELECT prod_id, prod_name, prod_price FROM products; +``` + +#### 查询所有列 + +```sql +SELECT * FROM products; +``` + +#### 查询不同的值 + +```sql +SELECT DISTINCT vend_id FROM products; +``` + +#### 限制查询数量 + +```sql +-- 返回前 5 行 +SELECT * FROM products LIMIT 5; +SELECT * FROM products LIMIT 0, 5; +-- 返回第 3 ~ 5 行 +SELECT * FROM products LIMIT 2, 3; +``` + +## 过滤数据(WHERE) + +子查询是嵌套在较大查询中的 SQL 查询。子查询也称为**内部查询**或**内部选择**,而包含子查询的语句也称为**外部查询**或**外部选择**。 + +- 子查询可以嵌套在 `SELECT`,`INSERT`,`UPDATE` 或 `DELETE` 语句内或另一个子查询中。 + +- 子查询通常会在另一个 `SELECT` 语句的 `WHERE` 子句中添加。 + +- 您可以使用比较运算符,如 `>`,`<`,或 `=`。比较运算符也可以是多行运算符,如 `IN`,`ANY` 或 `ALL`。 + +- 子查询必须被圆括号 `()` 括起来。 + +- 内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图: + +

+ sql-subqueries +

+ +**子查询的子查询** + +```sql +SELECT cust_name, cust_contact +FROM customers +WHERE cust_id IN (SELECT cust_id + FROM orders + WHERE order_num IN (SELECT order_num + FROM orderitems + WHERE prod_id = 'RGAN01')); +``` + +### WHERE 子句 + +在 SQL 语句中,数据根据 `WHERE` 子句中指定的搜索条件进行过滤。 + +`WHERE` 子句的基本格式如下: + +```sql +SELECT ……(列名) FROM ……(表名) WHERE ……(子句条件) +``` + +`WHERE` 子句用于过滤记录,即缩小访问数据的范围。`WHERE` 后跟一个返回 `true` 或 `false` 的条件。 + +`WHERE` 可以与 `SELECT`,`UPDATE` 和 `DELETE` 一起使用。 + +**`SELECT` 语句中的 `WHERE` 子句** + +```sql +SELECT * FROM Customers +WHERE cust_name = 'Kids Place'; +``` + +**`UPDATE` 语句中的 `WHERE` 子句** + +```sql +UPDATE Customers +SET cust_name = 'Jack Jones' +WHERE cust_name = 'Kids Place'; +``` + +**`DELETE` 语句中的 `WHERE` 子句** + +```sql +DELETE FROM Customers +WHERE cust_name = 'Kids Place'; +``` + +可以在 `WHERE` 子句中使用的操作符: + +### 比较操作符 + +| 运算符 | 描述 | +| ------ | ------------------------------------------------------ | +| `=` | 等于 | +| `<>` | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != | +| `>` | 大于 | +| `<` | 小于 | +| `>=` | 大于等于 | +| `<=` | 小于等于 | + +### 范围操作符 + +| 运算符 | 描述 | +| --------- | -------------------------- | +| `BETWEEN` | 在某个范围内 | +| `IN` | 指定针对某个列的多个可能值 | + +- `IN` 操作符在 `WHERE` 子句中使用,作用是在指定的几个特定值中任选一个值。 + +- `BETWEEN` 操作符在 `WHERE` 子句中使用,作用是选取介于某个范围内的值。 + +**IN 示例** + +```sql +SELECT * +FROM products +WHERE vend_id IN ('DLL01', 'BRS01'); +``` + +**BETWEEN 示例** + +```sql +SELECT * +FROM products +WHERE prod_price BETWEEN 3 AND 5; +``` + +### 逻辑操作符 + +| 运算符 | 描述 | +| ------ | ---------- | +| `AND` | 并且(与) | +| `OR` | 或者(或) | +| `NOT` | 否定(非) | + +`AND`、`OR`、`NOT` 是用于对过滤条件的逻辑处理指令。 + +- `AND` 优先级高于 `OR`,为了明确处理顺序,可以使用 `()`。`AND` 操作符表示左右条件都要满足。 +- `OR` 操作符表示左右条件满足任意一个即可。 + +- `NOT` 操作符用于否定一个条件。 + +**AND 示例** + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE vend_id = 'DLL01' AND prod_price <= 4; +``` + +**OR 示例** + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'; +``` + +**NOT 示例** + +```sql +SELECT * +FROM products +WHERE prod_price NOT BETWEEN 3 AND 5; +``` + +### 通配符 + +| 运算符 | 描述 | +| ------ | -------------------------- | +| `LIKE` | 搜索某种模式 | +| `%` | 表示任意字符出现任意次数 | +| `_` | 表示任意字符出现一次 | +| `[]` | 必须匹配指定位置的一个字符 | + +`LIKE` 操作符在 `WHERE` 子句中使用,作用是确定字符串是否匹配模式。只有字段是文本值时才使用 `LIKE`。 + +`LIKE` 支持以下通配符匹配选项: + +- `%` 表示任何字符出现任意次数。 +- `_` 表示任何字符出现一次。 +- `[]` 必须匹配指定位置的一个字符。 + +> 注意:**不要滥用通配符,通配符位于开头处匹配会非常慢**。 + +`%` 示例: + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE prod_name LIKE '%bean bag%'; +``` + +`_` 示例: + +```sql +SELECT prod_id, prod_name, prod_price +FROM products +WHERE prod_name LIKE '__ inch teddy bear'; +``` + +## 排序和分组 + +### ORDER BY + +> `ORDER BY` 用于对结果集进行排序。 + +`ORDER BY` 有两种排序模式: + +- `ASC` :升序(默认) +- `DESC` :降序 + +可以按多个列进行排序,并且为每个列指定不同的排序方式。 + +指定多个列的排序示例: + +```sql +SELECT * FROM products +ORDER BY prod_price DESC, prod_name ASC; +``` + +### GROUP BY + +> `GROUP BY` 子句将记录分组到汇总行中,`GROUP BY` 为每个组返回一个记录。 + +`GROUP BY` 可以按一列或多列进行分组。 + +`GROUP BY` 通常还涉及聚合函数:COUNT,MAX,SUM,AVG 等。 + +`GROUP BY` 按分组字段进行排序后,`ORDER BY` 可以以汇总字段来进行排序。 + +分组示例: + +```sql +SELECT cust_name, COUNT(cust_address) AS addr_num +FROM Customers GROUP BY cust_name; +``` + +分组后排序示例: + +```sql +SELECT cust_name, COUNT(cust_address) AS addr_num +FROM Customers GROUP BY cust_name +ORDER BY cust_name DESC; +``` + +### HAVING + +> `HAVING` 用于对汇总的 `GROUP BY` 结果进行过滤。`HAVING` 要求存在一个 `GROUP BY` 子句。 + +`WHERE` 和 `HAVING` 可以在相同的查询中。 + +`HAVING` vs `WHERE`: + +- `WHERE` 和 `HAVING` 都是用于过滤。 +- `HAVING` 适用于汇总的组记录;而 `WHERE` 适用于单个记录。 + +使用 `WHERE` 和 `HAVING` 过滤数据示例: + +```sql +SELECT cust_name, COUNT(*) AS num +FROM Customers +WHERE cust_email IS NOT NULL +GROUP BY cust_name +HAVING COUNT(*) >= 1; +``` + +## 连接和组合 + +### 连接(JOIN) + +**在 SELECT, UPDATE 和 DELETE 语句中,“连接”可以用于联合多表查询。连接使用 `JOIN` 关键字,并且条件语句使用 `ON` 而不是 `WHERE`**。 + +**连接可以替换子查询,并且一般比子查询的效率更快**。 + +`JOIN` 有以下类型: + +- 内连接 - 内连接又称等值连接,用于获取两个表中字段匹配关系的记录,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 + - 笛卡尔积 - **“笛卡尔积”也称为交叉连接(`CROSS JOIN`),它的作用就是可以把任意表进行连接,即使这两张表不相关**。 + - 自连接(=) - **“自连接(=)”可以看成内连接的一种,只是连接的表是自身而已**。 + - 自然连接(NATURAL JOIN) - **“自然连接”会自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 +- 外连接 + - 左连接(LEFT JOIN) - **“左外连接”会获取左表所有记录,即使右表没有对应匹配的记录**。左外连接使用 `LEFT JOIN` 关键字。 + - 右连接(RIGHT JOIN) - **“右外连接”会获取右表所有记录,即使左表没有对应匹配的记录**。右外连接使用 `RIGHT JOIN` 关键字。 + +
+ sql-join +
+#### 内连接(INNER JOIN) + +内连接又称等值连接,用于获取两个表中字段匹配关系的记录,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 + +```sql +SELECT vend_name, prod_name, prod_price +FROM vendors INNER JOIN products +ON vendors.vend_id = products.vend_id; + +-- 也可以省略 INNER 使用 JOIN,与上面一句效果一样 +SELECT vend_name, prod_name, prod_price +FROM vendors JOIN products +ON vendors.vend_id = products.vend_id; +``` + +##### 笛卡尔积 + +**“笛卡尔积”也称为交叉连接(`CROSS JOIN`),它的作用就是可以把任意表进行连接,即使这两张表不相关**。但通常进行连接还是需要筛选的,因此需要在连接后面加上 `WHERE` 子句,也就是作为过滤条件对连接数据进行筛选。 + +笛卡尔积是一个数学运算。假设我有两个集合 X 和 Y,那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合,也就是第一个对象来自于 X,第二个对象来自于 Y 的所有可能。 + +【示例】求 t1 和 t2 两张表的笛卡尔积 + +```sql +-- 以下两条 SQL,执行结果相同 +SELECT * FROM t1, t2; +SELECT * FROM t1 CROSS JOIN t2; +``` + +##### 自连接(=) + +**“自连接”可以看成内连接的一种,只是连接的表是自身而已**。 + +```sql +SELECT c1.cust_id, c1.cust_name, c1.cust_contact +FROM customers c1, customers c2 +WHERE c1.cust_name = c2.cust_name +AND c2.cust_contact = 'Jim Jones'; +``` + +##### 自然连接(NATURAL JOIN) + +**“自然连接”会自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 + +```sql +SELECT * +FROM Products +NATURAL JOIN Customers; +``` + +#### 外连接(OUTER JOIN) + +外连接返回一个表中的所有行,并且仅返回来自此表中满足连接条件的那些行,即两个表中的列是相等的。外连接分为左外连接、右外连接、全外连接(Mysql 不支持)。 + +##### 左连接(LEFT JOIN) + +**“左外连接”会获取左表所有记录,即使右表没有对应匹配的记录**。左外连接使用 `LEFT JOIN` 关键字。 + +```sql +SELECT customers.cust_id, orders.order_num +FROM customers LEFT JOIN orders +ON customers.cust_id = orders.cust_id; +``` + +##### 右连接(RIGHT JOIN) + +**“右外连接”会获取右表所有记录,即使左表没有对应匹配的记录**。右外连接使用 `RIGHT JOIN` 关键字。 + +```sql +SELECT customers.cust_id, orders.order_num +FROM customers RIGHT JOIN orders +ON customers.cust_id = orders.cust_id; +``` + +### 组合(UNION) + +> `UNION` 运算符**将两个或更多查询的结果组合起来,并生成一个结果集**,其中包含来自 `UNION` 中参与查询的提取行。 + +`UNION` 基本规则: + +- 所有查询的列数和列顺序必须相同。 +- 每个查询中涉及表的列的数据类型必须相同或兼容。 +- 通常返回的列名取自第一个查询。 + +默认会去除相同行,如果需要保留相同行,使用 `UNION ALL`。 + +只能包含一个 `ORDER BY` 子句,并且必须位于语句的最后。 + +应用场景: + +- 在一个查询中从不同的表返回结构数据。 +- 对一个表执行多个查询,按一个查询返回数据。 + +组合查询示例: + +```sql +SELECT cust_name, cust_contact, cust_email +FROM customers +WHERE cust_state IN ('IL', 'IN', 'MI') +UNION +SELECT cust_name, cust_contact, cust_email +FROM customers +WHERE cust_name = 'Fun4All'; +``` + +### JOIN vs UNION + +- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。 +- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。 + +## 函数 + +> 🔔 注意:不同数据库的函数往往各不相同,因此不可移植。本节主要以 Mysql 的函数为例。 + +### 字符串函数 + +| 函数 | 说明 | +| :------------------: | :--------------------: | +| `CONCAT()` | 合并字符串 | +| `LEFT()`、`RIGHT()` | 左边或者右边的字符 | +| `LOWER()`、`UPPER()` | 转换为小写或者大写 | +| `LTRIM()`、`RTIM()` | 去除左边或者右边的空格 | +| `LENGTH()` | 长度 | +| `SOUNDEX()` | 转换为语音值 | + +其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。 + +```sql +SELECT * +FROM mytable +WHERE SOUNDEX(col1) = SOUNDEX('apple') +``` + +### 时间函数 + +- 日期格式:`YYYY-MM-DD` +- 时间格式:`HH:MM:SS` + +| 函 数 | 说 明 | +| :--------------: | :----------------------------: | +| `ADDDATE()` | 增加一个日期(天、周等) | +| `ADDTIME()` | 增加一个时间(时、分等) | +| `CURRENT_DATE()` | 返回当前日期 | +| `CURRENT_TIME()` | 返回当前时间 | +| `DATE()` | 返回日期时间的日期部分 | +| `DATEDIFF()` | 计算两个日期之差 | +| `DATE_ADD()` | 高度灵活的日期运算函数 | +| `DATE_FORMAT()` | 返回一个格式化的日期或时间串 | +| `DAY()` | 返回一个日期的天数部分 | +| `DAYOFWEEK()` | 对于一个日期,返回对应的星期几 | +| `HOUR()` | 返回一个时间的小时部分 | +| `MINUTE()` | 返回一个时间的分钟部分 | +| `MONTH()` | 返回一个日期的月份部分 | +| `NOW()` | 返回当前日期和时间 | +| `SECOND()` | 返回一个时间的秒部分 | +| `TIME()` | 返回一个日期时间的时间部分 | +| `YEAR()` | 返回一个日期的年份部分 | + +```sql +mysql> SELECT NOW(); +2018-4-14 20:25:11 +``` + +### 数学函数 + +常见 Mysql 数学函数: + +| 函数 | 说明 | +| :-------: | :------: | +| `ABS()` | 取绝对值 | +| `MOD()` | 取余 | +| `ROUND()` | 四舍五入 | +| `...` | | + +### 聚合函数 + +| 函 数 | 说 明 | +| :-------: | :--------------: | +| `AVG()` | 返回某列的平均值 | +| `COUNT()` | 返回某列的行数 | +| `MAX()` | 返回某列的最大值 | +| `MIN()` | 返回某列的最小值 | +| `SUM()` | 返回某列值之和 | + +`AVG()` 会忽略 NULL 行。 + +使用 DISTINCT 可以让汇总函数值汇总不同的值。 + +```sql +SELECT AVG(DISTINCT col1) AS avg_col +FROM mytable +``` + +### 转换函数 + +| 函 数 | 说 明 | 示例 | +| :------: | :----------: | -------------------------------------------------- | +| `CAST()` | 转换数据类型 | `SELECT CAST("2017-08-29" AS DATE); -> 2017-08-29` | + +## 事务 + +不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 + +**MySQL 默认采用隐式提交策略(`autocommit`)**,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 + +通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 + +事务处理指令: + +- `START TRANSACTION` - 指令用于标记事务的起始点。 +- `SAVEPOINT` - 指令用于创建保留点。 +- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。 +- `COMMIT` - 提交事务。 +- `RELEASE SAVEPOINT`:删除某个保存点。 +- `SET TRANSACTION`:设置事务的隔离级别。 + +事务处理示例: + +```sql +-- 开始事务 +START TRANSACTION; + +-- 插入操作 A +INSERT INTO `user` +VALUES (1, 'root1', 'root1', 'xxxx@163.com'); + +-- 创建保留点 updateA +SAVEPOINT updateA; + +-- 插入操作 B +INSERT INTO `user` +VALUES (2, 'root2', 'root2', 'xxxx@163.com'); + +-- 回滚到保留点 updateA +ROLLBACK TO updateA; + +-- 提交事务,只有操作 A 生效 +COMMIT; +``` + +### ACID + +### 事务隔离级别 + +--- + +**(以下为 DCL 语句用法)** + +## 权限控制 + +`GRANT` 和 `REVOKE` 可在几个层次上控制访问权限: + +- 整个服务器,使用 `GRANT ALL` 和 `REVOKE ALL`; +- 整个数据库,使用 ON database.\*; +- 特定的表,使用 ON database.table; +- 特定的列; +- 特定的存储过程。 + +新创建的账户没有任何权限。 + +账户用 `username@host` 的形式定义,`username@%` 使用的是默认主机名。 + +MySQL 的账户信息保存在 mysql 这个数据库中。 + +```sql +USE mysql; +SELECT user FROM user; +``` + +### 创建账户 + +```sql +CREATE USER myuser IDENTIFIED BY 'mypassword'; +``` + +### 修改账户名 + +```sql +UPDATE user SET user='newuser' WHERE user='myuser'; +FLUSH PRIVILEGES; +``` + +### 删除账户 + +```sql +DROP USER myuser; +``` + +### 查看权限 + +```sql +SHOW GRANTS FOR myuser; +``` + +### 授予权限 + +```sql +GRANT SELECT, INSERT ON *.* TO myuser; +``` + +### 删除权限 + +```sql +REVOKE SELECT, INSERT ON *.* FROM myuser; +``` + +### 更改密码 + +```sql +SET PASSWORD FOR myuser = 'mypass'; +``` + +## 存储过程 + +存储过程的英文是 Stored Procedure。它可以视为一组 SQL 语句的批处理。一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。 + +定义存储过程的语法格式: + +```sql +CREATE PROCEDURE 存储过程名称 ([参数列表]) +BEGIN + 需要执行的语句 +END +``` + +存储过程定义语句类型: + +- `CREATE PROCEDURE` 用于创建存储过程 +- `DROP PROCEDURE` 用于删除存储过程 +- `ALTER PROCEDURE` 用于修改存储过程 + +### 使用存储过程 + +创建存储过程的要点: + +- `DELIMITER` 用于定义语句的结束符 +- 存储过程的 3 种参数类型: + - `IN`:存储过程的入参 + - `OUT`:存储过程的出参 + - `INPUT`:既是存储过程的入参,也是存储过程的出参 +- 流控制语句: + - `BEGIN…END`:`BEGIN…END` 中间包含了多个语句,每个语句都以(`;`)号为结束符。 + - `DECLARE`:`DECLARE` 用来声明变量,使用的位置在于 `BEGIN…END` 语句中间,而且需要在其他语句使用之前进行变量的声明。 + - `SET`:赋值语句,用于对变量进行赋值。 + - `SELECT…INTO`:把从数据表中查询的结果存放到变量中,也就是为变量赋值。每次只能给一个变量赋值,不支持集合的操作。 + - `IF…THEN…ENDIF`:条件判断语句,可以在 `IF…THEN…ENDIF` 中使用 `ELSE` 和 `ELSEIF` 来进行条件判断。 + - `CASE`:`CASE` 语句用于多条件的分支判断。 + +创建存储过程示例: + +```sql +DROP PROCEDURE IF EXISTS `proc_adder`; +DELIMITER ;; +CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int) +BEGIN + DECLARE c int; + if a is null then set a = 0; + end if; + + if b is null then set b = 0; + end if; + + set sum = a + b; +END +;; +DELIMITER ; +``` + +使用存储过程示例: + +```sql +set @b=5; +call proc_adder(2,@b,@s); +select @s as sum; +``` + +### 存储过程的利弊 + +存储过程的优点: + +- **执行效率高**:一次编译多次使用。 +- **安全性强**:在设定存储过程的时候可以设置对用户的使用权限,这样就和视图一样具有较强的安全性。 +- **可复用**:将代码封装,可以提高代码复用。 +- **性能好** + - 由于是预先编译,因此具有很高的性能。 + - 一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率。 + +存储过程的缺点: + +- **可移植性差**:存储过程不能跨数据库移植。由于不同数据库的存储过程语法几乎都不一样,十分难以维护(不通用)。 +- **调试困难**:只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。 +- **版本管理困难**:比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。 +- **不适合高并发的场景**:高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护,增加数据库的压力,显然就不适用了。 + +> _综上,存储过程的优缺点都非常突出,是否使用一定要慎重,需要根据具体应用场景来权衡_。 + +### 触发器 + +> 触发器可以视为一种特殊的存储过程。 +> +> 触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。 + +#### 触发器特性 + +可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 + +MySQL 不允许在触发器中使用 `CALL` 语句 ,也就是不能调用存储过程。 + +**`BEGIN` 和 `END`** + +当触发器的触发条件满足时,将会执行 `BEGIN` 和 `END` 之间的触发器执行动作。 + +> 🔔 注意:在 MySQL 中,分号 `;` 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。 +> +> 这时就会用到 `DELIMITER` 命令(`DELIMITER` 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。 + +**`NEW` 和 `OLD`** + +- MySQL 中定义了 `NEW` 和 `OLD` 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。 +- 在 `INSERT` 型触发器中,`NEW` 用来表示将要(`BEFORE`)或已经(`AFTER`)插入的新数据; +- 在 `UPDATE` 型触发器中,`OLD` 用来表示将要或已经被修改的原数据,`NEW` 用来表示将要或已经修改为的新数据; +- 在 `DELETE` 型触发器中,`OLD` 用来表示将要或已经被删除的原数据; +- 使用方法: `NEW.columnName` (columnName 为相应数据表某一列名) + +#### 触发器指令 + +> 提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。 + +`CREATE TRIGGER` 指令用于创建触发器。 + +语法: + +```sql +CREATE TRIGGER trigger_name +trigger_time +trigger_event +ON table_name +FOR EACH ROW +BEGIN + trigger_statements +END; +``` + +说明: + +- trigger_name:触发器名 +- trigger_time: 触发器的触发时机。取值为 `BEFORE` 或 `AFTER`。 +- trigger_event: 触发器的监听事件。取值为 `INSERT`、`UPDATE` 或 `DELETE`。 +- table_name: 触发器的监听目标。指定在哪张表上建立触发器。 +- FOR EACH ROW: 行级监视,Mysql 固定写法,其他 DBMS 不同。 +- trigger_statements: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 `;` 来结尾。 + +创建触发器示例: + +```sql +DELIMITER $ +CREATE TRIGGER `trigger_insert_user` +AFTER INSERT ON `user` +FOR EACH ROW +BEGIN + INSERT INTO `user_history`(user_id, operate_type, operate_time) + VALUES (NEW.id, 'add a user', now()); +END $ +DELIMITER ; +``` + +查看触发器示例: + +```sql +SHOW TRIGGERS; +``` + +删除触发器示例: + +```sql +DROP TRIGGER IF EXISTS trigger_insert_user; +``` + +## 游标 + +> 游标(CURSOR)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 `SELECT` 语句,而是被该语句检索出来的结果集。在存储过程中使用游标可以对一个结果集进行移动遍历。 + +游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 + +使用游标的步骤: + +1. **定义游标**:通过 `DECLARE cursor_name CURSOR FOR <语句>` 定义游标。这个过程没有实际检索出数据。 +2. **打开游标**:通过 `OPEN cursor_name` 打开游标。 +3. **取出数据**:通过 `FETCH cursor_name INTO var_name ...` 获取数据。 +4. **关闭游标**:通过 `CLOSE cursor_name` 关闭游标。 +5. **释放游标**:通过 `DEALLOCATE PREPARE` 释放游标。 + +游标使用示例: + +```sql +DELIMITER $ +CREATE PROCEDURE getTotal() +BEGIN + DECLARE total INT; + -- 创建接收游标数据的变量 + DECLARE sid INT; + DECLARE sname VARCHAR(10); + -- 创建总数变量 + DECLARE sage INT; + -- 创建结束标志变量 + DECLARE done INT DEFAULT false; + -- 创建游标 + DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; + -- 指定游标循环结束时的返回值 + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; + SET total = 0; + OPEN cur; + FETCH cur INTO sid, sname, sage; + WHILE(NOT done) + DO + SET total = total + 1; + FETCH cur INTO sid, sname, sage; + END WHILE; + + CLOSE cur; + SELECT total; +END $ +DELIMITER ; + +-- 调用存储过程 +call getTotal(); +``` + +## 参考资料 + +- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) +- [“浅入深出”MySQL 中事务的实现](https://draveness.me/mysql-transaction) +- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) +- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) +- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) +- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) +- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) +- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) +- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) +- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" deleted file mode 100644 index e018850d2d..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/02.SQL\350\257\255\346\263\225\345\237\272\347\241\200\347\211\271\346\200\247.md" +++ /dev/null @@ -1,601 +0,0 @@ ---- -title: SQL 语法基础特性 -date: 2018-06-15 16:07:17 -categories: - - 数据库 - - 关系型数据库 - - 综合 -tags: - - 数据库 - - 关系型数据库 - - SQL -permalink: /pages/b71c9e/ ---- - -# SQL 语法基础特性 - -> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 -> -> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200115160512.png) - -## SQL 简介 - -### 数据库术语 - -- `数据库(database)` - 保存有组织的数据的容器(通常是一个文件或一组文件)。 -- `数据表(table)` - 某种特定类型数据的结构化清单。 -- `模式(schema)` - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。 -- `列(column)` - 表中的一个字段。所有表都是由一个或多个列组成的。 -- `行(row)` - 表中的一个记录。 -- `主键(primary key)` - 一列(或一组列),其值能够唯一标识表中每一行。 - -### SQL 语法 - -> SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 - -#### SQL 语法结构 - -![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/sql-syntax.png) - -SQL 语法结构包括: - -- **`子句`** - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。) -- **`表达式`** - 可以产生任何标量值,或由列和行的数据库表 -- **`谓词`** - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。 -- **`查询`** - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。 -- **`语句`** - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。 - -#### SQL 语法要点 - -- **SQL 语句不区分大小写**,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。 - -例如:`SELECT` 与 `select` 、`Select` 是相同的。 - -- **多条 SQL 语句必须以分号(`;`)分隔**。 - -- 处理 SQL 语句时,**所有空格都被忽略**。SQL 语句可以写成一行,也可以分写为多行。 - -```sql --- 一行 SQL 语句 -UPDATE user SET username='robot', password='robot' WHERE username = 'root'; - --- 多行 SQL 语句 -UPDATE user -SET username='robot', password='robot' -WHERE username = 'root'; -``` - -- SQL 支持三种注释 - -```sql -## 注释1 --- 注释2 -/* 注释3 */ -``` - -#### SQL 分类 - -#### 数据定义语言(DDL) - -数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。 - -DDL 的主要功能是**定义数据库对象**。 - -DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。 - -#### 数据操纵语言(DML) - -数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库其中的对象和数据运行访问工作的编程语句。 - -DML 的主要功能是 **访问数据**,因此其语法都是以**读写数据库**为主。 - -DML 的核心指令是 `INSERT`、`UPDATE`、`DELETE`、`SELECT`。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。 - -#### 事务控制语言(TCL) - -事务控制语言 (Transaction Control Language, TCL) 用于**管理数据库中的事务**。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。 - -TCL 的核心指令是 `COMMIT`、`ROLLBACK`。 - -#### 数据控制语言(DCL) - -数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。 - -DCL 的核心指令是 `GRANT`、`REVOKE`。 - -DCL 以**控制用户的访问权限**为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:`CONNECT`、`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`EXECUTE`、`USAGE`、`REFERENCES`。 - -根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。 - ---- - -**(以下为 DML 语句用法)** - -## 增删改查(CRUD) - -增删改查,又称为 **`CRUD`**,是数据库基本操作中的基本操作。 - -### 插入数据 - -> - `INSERT INTO` 语句用于向表中插入新记录。 - -#### 插入完整的行 - -```sql -INSERT INTO user -VALUES (10, 'root', 'root', 'xxxx@163.com'); -``` - -#### 插入行的一部分 - -```sql -INSERT INTO user(username, password, email) -VALUES ('admin', 'admin', 'xxxx@163.com'); -``` - -#### 插入查询出来的数据 - -```sql -INSERT INTO user(username) -SELECT name -FROM account; -``` - -### 更新数据 - -> - `UPDATE` 语句用于更新表中的记录。 - -```sql -UPDATE user -SET username='robot', password='robot' -WHERE username = 'root'; -``` - -### 删除数据 - -> - `DELETE` 语句用于删除表中的记录。 -> - `TRUNCATE TABLE` 可以清空表,也就是删除所有行。 - -#### 删除表中的指定数据 - -```sql -DELETE FROM user WHERE username = 'robot'; -``` - -#### 清空表中的数据 - -```sql -TRUNCATE TABLE user; -``` - -### 查询数据 - -> - `SELECT` 语句用于从数据库中查询数据。 -> - `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 -> - `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 -> - `ASC` :升序(默认) -> - `DESC` :降序 - -#### 查询单列 - -```sql -SELECT prod_name FROM products; -``` - -#### 查询多列 - -```sql -SELECT prod_id, prod_name, prod_price FROM products; -``` - -#### 查询所有列 - -```sql -SELECT * FROM products; -``` - -#### 查询不同的值 - -```sql -SELECT DISTINCT vend_id FROM products; -``` - -#### 限制查询数量 - -```sql --- 返回前 5 行 -SELECT * FROM products LIMIT 5; -SELECT * FROM products LIMIT 0, 5; --- 返回第 3 ~ 5 行 -SELECT * FROM products LIMIT 2, 3; -``` - -## 过滤数据(WHERE) - -子查询是嵌套在较大查询中的 SQL 查询。子查询也称为**内部查询**或**内部选择**,而包含子查询的语句也称为**外部查询**或**外部选择**。 - -- 子查询可以嵌套在 `SELECT`,`INSERT`,`UPDATE` 或 `DELETE` 语句内或另一个子查询中。 - -- 子查询通常会在另一个 `SELECT` 语句的 `WHERE` 子句中添加。 - -- 您可以使用比较运算符,如 `>`,`<`,或 `=`。比较运算符也可以是多行运算符,如 `IN`,`ANY` 或 `ALL`。 - -- 子查询必须被圆括号 `()` 括起来。 - -- 内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图: - -

- sql-subqueries -

- -**子查询的子查询** - -```sql -SELECT cust_name, cust_contact -FROM customers -WHERE cust_id IN (SELECT cust_id - FROM orders - WHERE order_num IN (SELECT order_num - FROM orderitems - WHERE prod_id = 'RGAN01')); -``` - -### WHERE 子句 - -在 SQL 语句中,数据根据 `WHERE` 子句中指定的搜索条件进行过滤。 - -`WHERE` 子句的基本格式如下: - -```sql -SELECT ……(列名) FROM ……(表名) WHERE ……(子句条件) -``` - -`WHERE` 子句用于过滤记录,即缩小访问数据的范围。`WHERE` 后跟一个返回 `true` 或 `false` 的条件。 - -`WHERE` 可以与 `SELECT`,`UPDATE` 和 `DELETE` 一起使用。 - -**`SELECT` 语句中的 `WHERE` 子句** - -```sql -SELECT * FROM Customers -WHERE cust_name = 'Kids Place'; -``` - -**`UPDATE` 语句中的 `WHERE` 子句** - -```sql -UPDATE Customers -SET cust_name = 'Jack Jones' -WHERE cust_name = 'Kids Place'; -``` - -**`DELETE` 语句中的 `WHERE` 子句** - -```sql -DELETE FROM Customers -WHERE cust_name = 'Kids Place'; -``` - -可以在 `WHERE` 子句中使用的操作符: - -### 比较操作符 - -| 运算符 | 描述 | -| ------ | ------------------------------------------------------ | -| `=` | 等于 | -| `<>` | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != | -| `>` | 大于 | -| `<` | 小于 | -| `>=` | 大于等于 | -| `<=` | 小于等于 | - -### 范围操作符 - -| 运算符 | 描述 | -| --------- | -------------------------- | -| `BETWEEN` | 在某个范围内 | -| `IN` | 指定针对某个列的多个可能值 | - -- `IN` 操作符在 `WHERE` 子句中使用,作用是在指定的几个特定值中任选一个值。 - -- `BETWEEN` 操作符在 `WHERE` 子句中使用,作用是选取介于某个范围内的值。 - -**IN 示例** - -```sql -SELECT * -FROM products -WHERE vend_id IN ('DLL01', 'BRS01'); -``` - -**BETWEEN 示例** - -```sql -SELECT * -FROM products -WHERE prod_price BETWEEN 3 AND 5; -``` - -### 逻辑操作符 - -| 运算符 | 描述 | -| ------ | ---------- | -| `AND` | 并且(与) | -| `OR` | 或者(或) | -| `NOT` | 否定(非) | - -`AND`、`OR`、`NOT` 是用于对过滤条件的逻辑处理指令。 - -- `AND` 优先级高于 `OR`,为了明确处理顺序,可以使用 `()`。`AND` 操作符表示左右条件都要满足。 -- `OR` 操作符表示左右条件满足任意一个即可。 - -- `NOT` 操作符用于否定一个条件。 - -**AND 示例** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE vend_id = 'DLL01' AND prod_price <= 4; -``` - -**OR 示例** - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE vend_id = 'DLL01' OR vend_id = 'BRS01'; -``` - -**NOT 示例** - -```sql -SELECT * -FROM products -WHERE prod_price NOT BETWEEN 3 AND 5; -``` - -### 通配符 - -| 运算符 | 描述 | -| ------ | -------------------------- | -| `LIKE` | 搜索某种模式 | -| `%` | 表示任意字符出现任意次数 | -| `_` | 表示任意字符出现一次 | -| `[]` | 必须匹配指定位置的一个字符 | - -`LIKE` 操作符在 `WHERE` 子句中使用,作用是确定字符串是否匹配模式。只有字段是文本值时才使用 `LIKE`。 - -`LIKE` 支持以下通配符匹配选项: - -- `%` 表示任何字符出现任意次数。 -- `_` 表示任何字符出现一次。 -- `[]` 必须匹配指定位置的一个字符。 - -> 注意:**不要滥用通配符,通配符位于开头处匹配会非常慢**。 - -`%` 示例: - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE prod_name LIKE '%bean bag%'; -``` - -`_` 示例: - -```sql -SELECT prod_id, prod_name, prod_price -FROM products -WHERE prod_name LIKE '__ inch teddy bear'; -``` - -- - -## 排序(ORDER BY) - -> `ORDER BY` 用于对结果集进行排序。 - -`ORDER BY` 有两种排序模式: - -- `ASC` :升序(默认) -- `DESC` :降序 - -可以按多个列进行排序,并且为每个列指定不同的排序方式。 - -指定多个列的排序示例: - -```sql -SELECT * FROM products -ORDER BY prod_price DESC, prod_name ASC; -``` - -## 数据定义(CREATE、ALTER、DROP) - -> DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)。 - -### 数据库(DATABASE) - -#### 创建数据库 - -```sql -CREATE DATABASE IF NOT EXISTS db_tutorial; -``` - -#### 删除数据库 - -```sql -DROP DATABASE IF EXISTS db_tutorial; -``` - -#### 选择数据库 - -```sql -USE db_tutorial; -``` - -### 数据表(TABLE) - -#### 删除数据表 - -```sql -DROP TABLE IF EXISTS user; -DROP TABLE IF EXISTS vip_user; -``` - -#### 创建数据表 - -**普通创建** - -```sql -CREATE TABLE user ( - id INT(10) UNSIGNED NOT NULL COMMENT 'Id', - username VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '用户名', - password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', - email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' -) COMMENT ='用户表'; -``` - -**根据已有的表创建新表** - -```sql -CREATE TABLE vip_user AS -SELECT * -FROM user; -``` - -#### 修改数据表 - -##### 添加列 - -```sql -ALTER TABLE user -ADD age int(3); -``` - -##### 删除列 - -```sql -ALTER TABLE user -DROP COLUMN age; -``` - -##### 修改列 - -```sql -ALTER TABLE `user` -MODIFY COLUMN age tinyint; -``` - -### 视图(VIEW) - -> 视图是基于 SQL 语句的结果集的可视化的表。**视图是虚拟的表,本身不存储数据,也就不能对其进行索引操作**。对视图的操作和对普通表的操作一样。 - -视图的作用: - -- 简化复杂的 SQL 操作,比如复杂的连接。 -- 只使用实际表的一部分数据。 -- 通过只给用户访问视图的权限,保证数据的安全性。 -- 更改数据格式和表示。 - -#### 创建视图 - -```sql -CREATE VIEW top_10_user_view AS -SELECT id, username -FROM user -WHERE id < 10; -``` - -#### 删除视图 - -```sql -DROP VIEW top_10_user_view; -``` - -### 索引(INDEX) - -> 通过索引可以更加快速高效地查询数据。用户无法看到索引,它们只能被用来加速查询。 - -更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新。因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引。 - -唯一索引:唯一索引表明此索引的每一个索引值只对应唯一的数据记录。 - -#### 创建索引 - -```sql -CREATE INDEX idx_email - ON user(email); -``` - -#### 创建唯一索引 - -```sql -CREATE UNIQUE INDEX uniq_username - ON user(username); -``` - -#### 删除索引 - -```sql -ALTER TABLE user -DROP INDEX idx_email; -ALTER TABLE user -DROP INDEX uniq_username; -``` - -#### 添加主键 - -```sql -ALTER TABLE user -ADD PRIMARY KEY (id); -``` - -#### 删除主键 - -```sql -ALTER TABLE user -DROP PRIMARY KEY; -``` - -### 约束 - -> SQL 约束用于规定表中的数据规则。 - -- 如果存在违反约束的数据行为,行为会被约束终止。 -- 约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。 -- 约束类型 - - `NOT NULL` - 指示某列不能存储 NULL 值。 - - `UNIQUE` - 保证某列的每行必须有唯一的值。 - - `PRIMARY KEY` - NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。 - - `FOREIGN KEY` - 保证一个表中的数据匹配另一个表中的值的参照完整性。 - - `CHECK` - 保证列中的值符合指定的条件。 - - `DEFAULT` - 规定没有给列赋值时的默认值。 - -创建表时使用约束条件: - -```sql -CREATE TABLE Users ( - Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id', - Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名', - Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', - Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址', - Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效', - PRIMARY KEY (Id) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -``` - -## 参考资料 - -- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) -- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) -- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) -- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) -- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) -- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) -- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) -- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) -- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) -- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" deleted file mode 100644 index 6e489ff2c0..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.SQL\350\257\255\346\263\225\351\253\230\347\272\247\347\211\271\346\200\247.md" +++ /dev/null @@ -1,610 +0,0 @@ ---- -title: SQL 语法高级特性 -date: 2022-04-27 22:13:55 -categories: - - 数据库 - - 关系型数据库 - - 综合 -tags: - - 数据库 - - 关系型数据库 - - SQL -permalink: /pages/1ae1ca/ ---- - -# SQL 语法高级特性 - -> 本文针对关系型数据库的基本语法。限于篇幅,本文侧重说明用法,不会展开讲解特性、原理。 -> -> 本文语法主要针对 Mysql,但大部分的语法对其他关系型数据库也适用。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200115160512.png) - -## 连接和组合 - -### 连接(JOIN) - -> 连接用于连接多个表,使用 `JOIN` 关键字,并且条件语句使用 `ON` 而不是 `WHERE`。 - -如果一个 `JOIN` 至少有一个公共字段并且它们之间存在关系,则该 `JOIN` 可以在两个或多个表上工作。 - -`JOIN` 保持基表(结构和数据)不变。**连接可以替换子查询,并且比子查询的效率一般会更快**。 - -`JOIN` 有两种连接类型:内连接和外连接。 - -
- sql-join -
- -#### 内连接(INNER JOIN) - -内连接又称等值连接,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 - -```sql -SELECT vend_name, prod_name, prod_price -FROM vendors INNER JOIN products -ON vendors.vend_id = products.vend_id; -``` - -##### 自连接(`=`) - -自连接可以看成内连接的一种,只是**连接的表是自身**而已。**自然连接是把同名列通过 `=` 连接起来**的,同名列可以有多个。 - -```sql -SELECT c1.cust_id, c1.cust_name, c1.cust_contact -FROM customers c1, customers c2 -WHERE c1.cust_name = c2.cust_name -AND c2.cust_contact = 'Jim Jones'; -``` - -##### 自然连接(NATURAL JOIN) - -内连接提供连接的列,而自然连接**自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 - -```sql -SELECT * -FROM Products -NATURAL JOIN Customers; -``` - -#### 外连接(OUTER JOIN) - -外连接返回一个表中的所有行,并且仅返回来自此表中满足连接条件的那些行,即两个表中的列是相等的。外连接分为左外连接、右外连接、全外连接(Mysql 不支持)。 - -##### 左连接(LEFT JOIN) - -左外连接就是保留左表没有关联的行。 - -```sql -SELECT customers.cust_id, orders.order_num -FROM customers LEFT JOIN orders -ON customers.cust_id = orders.cust_id; -``` - -##### 右连接(RIGHT JOIN) - -右外连接就是保留右表没有关联的行。 - -```sql -SELECT customers.cust_id, orders.order_num -FROM customers RIGHT JOIN orders -ON customers.cust_id = orders.cust_id; -``` - -### 组合(UNION) - -> `UNION` 运算符**将两个或更多查询的结果组合起来,并生成一个结果集**,其中包含来自 `UNION` 中参与查询的提取行。 - -`UNION` 基本规则: - -- 所有查询的列数和列顺序必须相同。 -- 每个查询中涉及表的列的数据类型必须相同或兼容。 -- 通常返回的列名取自第一个查询。 - -默认会去除相同行,如果需要保留相同行,使用 `UNION ALL`。 - -只能包含一个 `ORDER BY` 子句,并且必须位于语句的最后。 - -应用场景: - -- 在一个查询中从不同的表返回结构数据。 -- 对一个表执行多个查询,按一个查询返回数据。 - -组合查询示例: - -```sql -SELECT cust_name, cust_contact, cust_email -FROM customers -WHERE cust_state IN ('IL', 'IN', 'MI') -UNION -SELECT cust_name, cust_contact, cust_email -FROM customers -WHERE cust_name = 'Fun4All'; -``` - -### JOIN vs UNION - -- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。 -- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。 - -## 函数 - -> 🔔 注意:不同数据库的函数往往各不相同,因此不可移植。本节主要以 Mysql 的函数为例。 - -### 文本处理 - -| 函数 | 说明 | -| :------------------: | :--------------------: | -| `LEFT()`、`RIGHT()` | 左边或者右边的字符 | -| `LOWER()`、`UPPER()` | 转换为小写或者大写 | -| `LTRIM()`、`RTIM()` | 去除左边或者右边的空格 | -| `LENGTH()` | 长度 | -| `SOUNDEX()` | 转换为语音值 | - -其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。 - -```sql -SELECT * -FROM mytable -WHERE SOUNDEX(col1) = SOUNDEX('apple') -``` - -### 日期和时间处理 - -- 日期格式:`YYYY-MM-DD` -- 时间格式:`HH:MM:SS` - -| 函 数 | 说 明 | -| :-------------: | :----------------------------: | -| `AddDate()` | 增加一个日期(天、周等) | -| `AddTime()` | 增加一个时间(时、分等) | -| `CurDate()` | 返回当前日期 | -| `CurTime()` | 返回当前时间 | -| `Date()` | 返回日期时间的日期部分 | -| `DateDiff()` | 计算两个日期之差 | -| `Date_Add()` | 高度灵活的日期运算函数 | -| `Date_Format()` | 返回一个格式化的日期或时间串 | -| `Day()` | 返回一个日期的天数部分 | -| `DayOfWeek()` | 对于一个日期,返回对应的星期几 | -| `Hour()` | 返回一个时间的小时部分 | -| `Minute()` | 返回一个时间的分钟部分 | -| `Month()` | 返回一个日期的月份部分 | -| `Now()` | 返回当前日期和时间 | -| `Second()` | 返回一个时间的秒部分 | -| `Time()` | 返回一个日期时间的时间部分 | -| `Year()` | 返回一个日期的年份部分 | - -```sql -mysql> SELECT NOW(); -``` - -``` -2018-4-14 20:25:11 -``` - -### 数值处理 - -| 函数 | 说明 | -| :----: | :----: | -| SIN() | 正弦 | -| COS() | 余弦 | -| TAN() | 正切 | -| ABS() | 绝对值 | -| SQRT() | 平方根 | -| MOD() | 余数 | -| EXP() | 指数 | -| PI() | 圆周率 | -| RAND() | 随机数 | - -### 汇总 - -| 函 数 | 说 明 | -| :-------: | :--------------: | -| `AVG()` | 返回某列的平均值 | -| `COUNT()` | 返回某列的行数 | -| `MAX()` | 返回某列的最大值 | -| `MIN()` | 返回某列的最小值 | -| `SUM()` | 返回某列值之和 | - -`AVG()` 会忽略 NULL 行。 - -使用 DISTINCT 可以让汇总函数值汇总不同的值。 - -```sql -SELECT AVG(DISTINCT col1) AS avg_col -FROM mytable -``` - -## 分组 - -### GROUP BY - -> `GROUP BY` 子句将记录分组到汇总行中,`GROUP BY` 为每个组返回一个记录。 - -`GROUP BY` 可以按一列或多列进行分组。 - -`GROUP BY` 通常还涉及聚合函数:COUNT,MAX,SUM,AVG 等。 - -`GROUP BY` 按分组字段进行排序后,`ORDER BY` 可以以汇总字段来进行排序。 - -分组示例: - -```sql -SELECT cust_name, COUNT(cust_address) AS addr_num -FROM Customers GROUP BY cust_name; -``` - -分组后排序示例: - -```sql -SELECT cust_name, COUNT(cust_address) AS addr_num -FROM Customers GROUP BY cust_name -ORDER BY cust_name DESC; -``` - -### HAVING - -> `HAVING` 用于对汇总的 `GROUP BY` 结果进行过滤。`HAVING` 要求存在一个 `GROUP BY` 子句。 - -`WHERE` 和 `HAVING` 可以在相同的查询中。 - -`HAVING` vs `WHERE`: - -- `WHERE` 和 `HAVING` 都是用于过滤。 -- `HAVING` 适用于汇总的组记录;而 `WHERE` 适用于单个记录。 - -使用 `WHERE` 和 `HAVING` 过滤数据示例: - -```sql -SELECT cust_name, COUNT(*) AS num -FROM Customers -WHERE cust_email IS NOT NULL -GROUP BY cust_name -HAVING COUNT(*) >= 1; -``` - ---- - -**(以下为 DDL 语句用法)** - -## 事务 - -不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 - -**MySQL 默认采用隐式提交策略(`autocommit`)**,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 - -通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 - -事务处理指令: - -- `START TRANSACTION` - 指令用于标记事务的起始点。 -- `SAVEPOINT` - 指令用于创建保留点。 -- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。 -- `COMMIT` - 提交事务。 -- `RELEASE SAVEPOINT`:删除某个保存点。 -- `SET TRANSACTION`:设置事务的隔离级别。 - -事务处理示例: - -```sql --- 开始事务 -START TRANSACTION; - --- 插入操作 A -INSERT INTO `user` -VALUES (1, 'root1', 'root1', 'xxxx@163.com'); - --- 创建保留点 updateA -SAVEPOINT updateA; - --- 插入操作 B -INSERT INTO `user` -VALUES (2, 'root2', 'root2', 'xxxx@163.com'); - --- 回滚到保留点 updateA -ROLLBACK TO updateA; - --- 提交事务,只有操作 A 生效 -COMMIT; -``` - -### ACID - -### 事务隔离级别 - ---- - -**(以下为 DCL 语句用法)** - -## 权限控制 - -`GRANT` 和 `REVOKE` 可在几个层次上控制访问权限: - -- 整个服务器,使用 `GRANT ALL` 和 `REVOKE ALL`; -- 整个数据库,使用 ON database.\*; -- 特定的表,使用 ON database.table; -- 特定的列; -- 特定的存储过程。 - -新创建的账户没有任何权限。 - -账户用 `username@host` 的形式定义,`username@%` 使用的是默认主机名。 - -MySQL 的账户信息保存在 mysql 这个数据库中。 - -```sql -USE mysql; -SELECT user FROM user; -``` - -### 创建账户 - -```sql -CREATE USER myuser IDENTIFIED BY 'mypassword'; -``` - -### 修改账户名 - -```sql -UPDATE user SET user='newuser' WHERE user='myuser'; -FLUSH PRIVILEGES; -``` - -### 删除账户 - -```sql -DROP USER myuser; -``` - -### 查看权限 - -```sql -SHOW GRANTS FOR myuser; -``` - -### 授予权限 - -```sql -GRANT SELECT, INSERT ON *.* TO myuser; -``` - -### 删除权限 - -```sql -REVOKE SELECT, INSERT ON *.* FROM myuser; -``` - -### 更改密码 - -```sql -SET PASSWORD FOR myuser = 'mypass'; -``` - -## 存储过程 - -存储过程的英文是 Stored Procedure。它可以视为一组 SQL 语句的批处理。一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。 - -定义存储过程的语法格式: - -```sql -CREATE PROCEDURE 存储过程名称 ([参数列表]) -BEGIN - 需要执行的语句 -END -``` - -存储过程定义语句类型: - -- `CREATE PROCEDURE` 用于创建存储过程 -- `DROP PROCEDURE` 用于删除存储过程 -- `ALTER PROCEDURE` 用于修改存储过程 - -### 使用存储过程 - -创建存储过程的要点: - -- `DELIMITER` 用于定义语句的结束符 -- 存储过程的 3 种参数类型: - - `IN`:存储过程的入参 - - `OUT`:存储过程的出参 - - `INPUT`:既是存储过程的入参,也是存储过程的出参 -- 流控制语句: - - `BEGIN…END`:`BEGIN…END` 中间包含了多个语句,每个语句都以(`;`)号为结束符。 - - `DECLARE`:`DECLARE` 用来声明变量,使用的位置在于 `BEGIN…END` 语句中间,而且需要在其他语句使用之前进行变量的声明。 - - `SET`:赋值语句,用于对变量进行赋值。 - - `SELECT…INTO`:把从数据表中查询的结果存放到变量中,也就是为变量赋值。每次只能给一个变量赋值,不支持集合的操作。 - - `IF…THEN…ENDIF`:条件判断语句,可以在 `IF…THEN…ENDIF` 中使用 `ELSE` 和 `ELSEIF` 来进行条件判断。 - - `CASE`:`CASE` 语句用于多条件的分支判断。 - -创建存储过程示例: - -```sql -DROP PROCEDURE IF EXISTS `proc_adder`; -DELIMITER ;; -CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int) -BEGIN - DECLARE c int; - if a is null then set a = 0; - end if; - - if b is null then set b = 0; - end if; - - set sum = a + b; -END -;; -DELIMITER ; -``` - -使用存储过程示例: - -```sql -set @b=5; -call proc_adder(2,@b,@s); -select @s as sum; -``` - -### 存储过程的利弊 - -存储过程的优点: - -- **执行效率高**:一次编译多次使用。 -- **安全性强**:在设定存储过程的时候可以设置对用户的使用权限,这样就和视图一样具有较强的安全性。 -- **可复用**:将代码封装,可以提高代码复用。 -- **性能好** - - 由于是预先编译,因此具有很高的性能。 - - 一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率。 - -存储过程的缺点: - -- **可移植性差**:存储过程不能跨数据库移植。由于不同数据库的存储过程语法几乎都不一样,十分难以维护(不通用)。 -- **调试困难**:只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。 -- **版本管理困难**:比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。 -- **不适合高并发的场景**:高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护,增加数据库的压力,显然就不适用了。 - -> _综上,存储过程的优缺点都非常突出,是否使用一定要慎重,需要根据具体应用场景来权衡_。 - -### 触发器 - -> 触发器可以视为一种特殊的存储过程。 -> -> 触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。 - -#### 触发器特性 - -可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 - -MySQL 不允许在触发器中使用 `CALL` 语句 ,也就是不能调用存储过程。 - -**`BEGIN` 和 `END`** - -当触发器的触发条件满足时,将会执行 `BEGIN` 和 `END` 之间的触发器执行动作。 - -> 🔔 注意:在 MySQL 中,分号 `;` 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。 -> -> 这时就会用到 `DELIMITER` 命令(`DELIMITER` 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。 - -**`NEW` 和 `OLD`** - -- MySQL 中定义了 `NEW` 和 `OLD` 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。 -- 在 `INSERT` 型触发器中,`NEW` 用来表示将要(`BEFORE`)或已经(`AFTER`)插入的新数据; -- 在 `UPDATE` 型触发器中,`OLD` 用来表示将要或已经被修改的原数据,`NEW` 用来表示将要或已经修改为的新数据; -- 在 `DELETE` 型触发器中,`OLD` 用来表示将要或已经被删除的原数据; -- 使用方法: `NEW.columnName` (columnName 为相应数据表某一列名) - -#### 触发器指令 - -> 提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。 - -`CREATE TRIGGER` 指令用于创建触发器。 - -语法: - -```sql -CREATE TRIGGER trigger_name -trigger_time -trigger_event -ON table_name -FOR EACH ROW -BEGIN - trigger_statements -END; -``` - -说明: - -- trigger_name:触发器名 -- trigger_time: 触发器的触发时机。取值为 `BEFORE` 或 `AFTER`。 -- trigger_event: 触发器的监听事件。取值为 `INSERT`、`UPDATE` 或 `DELETE`。 -- table_name: 触发器的监听目标。指定在哪张表上建立触发器。 -- FOR EACH ROW: 行级监视,Mysql 固定写法,其他 DBMS 不同。 -- trigger_statements: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 `;` 来结尾。 - -创建触发器示例: - -```sql -DELIMITER $ -CREATE TRIGGER `trigger_insert_user` -AFTER INSERT ON `user` -FOR EACH ROW -BEGIN - INSERT INTO `user_history`(user_id, operate_type, operate_time) - VALUES (NEW.id, 'add a user', now()); -END $ -DELIMITER ; -``` - -查看触发器示例: - -```sql -SHOW TRIGGERS; -``` - -删除触发器示例: - -```sql -DROP TRIGGER IF EXISTS trigger_insert_user; -``` - -## 游标 - -> 游标(CURSOR)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 `SELECT` 语句,而是被该语句检索出来的结果集。在存储过程中使用游标可以对一个结果集进行移动遍历。 - -游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 - -使用游标的步骤: - -1. **定义游标**:通过 `DECLARE cursor_name CURSOR FOR <语句>` 定义游标。这个过程没有实际检索出数据。 -2. **打开游标**:通过 `OPEN cursor_name` 打开游标。 -3. **取出数据**:通过 `FETCH cursor_name INTO var_name ...` 获取数据。 -4. **关闭游标**:通过 `CLOSE cursor_name` 关闭游标。 -5. **释放游标**:通过 `DEALLOCATE PREPARE` 释放游标。 - -游标使用示例: - -```sql -DELIMITER $ -CREATE PROCEDURE getTotal() -BEGIN - DECLARE total INT; - -- 创建接收游标数据的变量 - DECLARE sid INT; - DECLARE sname VARCHAR(10); - -- 创建总数变量 - DECLARE sage INT; - -- 创建结束标志变量 - DECLARE done INT DEFAULT false; - -- 创建游标 - DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30; - -- 指定游标循环结束时的返回值 - DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true; - SET total = 0; - OPEN cur; - FETCH cur INTO sid, sname, sage; - WHILE(NOT done) - DO - SET total = total + 1; - FETCH cur INTO sid, sname, sage; - END WHILE; - - CLOSE cur; - SELECT total; -END $ -DELIMITER ; - --- 调用存储过程 -call getTotal(); -``` - -## 参考资料 - -- [《SQL 必知必会》](https://book.douban.com/subject/35167240/) -- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction) -- [MySQL 的学习--触发器](https://www.cnblogs.com/CraryPrimitiveMan/p/4206942.html) -- [维基百科词条 - SQL](https://zh.wikipedia.org/wiki/SQL) -- [https://www.sitesbay.com/sql/index](https://www.sitesbay.com/sql/index) -- [SQL Subqueries](https://www.w3resource.com/sql/subqueries/understanding-sql-subqueries.php) -- [Quick breakdown of the types of joins](https://stackoverflow.com/questions/6294778/mysql-quick-breakdown-of-the-types-of-joins) -- [SQL UNION](https://www.w3resource.com/sql/sql-union.php) -- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php) -- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" index 4b80d891ce..c6a9bbdfbd 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/03.\346\211\251\345\261\225SQL.md" @@ -1,6 +1,7 @@ --- title: 扩展 SQL date: 2020-10-10 19:03:05 +order: 03 categories: - 数据库 - 关系型数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" index d152f9df05..2b1362fea0 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/99.SqlCheatSheet.md" @@ -1,6 +1,7 @@ --- title: SQL Cheat Sheet date: 2022-07-16 14:17:08 +order: 99 categories: - 数据库 - 关系型数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" index 4db87d95ab..c1a0b8fd3a 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/01.\347\273\274\345\220\210/README.md" @@ -10,17 +10,16 @@ tags: - 关系型数据库 permalink: /pages/22f2e3/ hidden: true +index: false --- # 关系型数据库综合知识 ## 📖 内容 -### [关系型数据库面试总结](01.关系型数据库面试.md) 💯 +### [数据库系统概论](01.数据库系统概论.md) -### [SQL 语法基础特性](02.SQL语法基础特性.md) - -### [SQL 语法高级特性](03.SQL语法高级特性.md) +### [SQL 语法速成](02.SQL语法.md) ### [扩展 SQL](03.扩展SQL.md) @@ -43,4 +42,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" deleted file mode 100644 index 205dacb408..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\345\272\224\347\224\250\346\214\207\345\215\227.md" +++ /dev/null @@ -1,225 +0,0 @@ ---- -title: Mysql 应用指南 -date: 2020-07-13 10:08:37 -categories: - - 数据库 - - 关系型数据库 - - Mysql -tags: - - 数据库 - - 关系型数据库 - - Mysql -permalink: /pages/5fe0f3/ ---- - -# Mysql 应用指南 - -## SQL 执行过程 - -学习 Mysql,最好是先从宏观上了解 Mysql 工作原理。 - -> 参考:[Mysql 工作流](02.MySQL工作流.md) - -## 存储引擎 - -在文件系统中,Mysql 将每个数据库(也可以成为 schema)保存为数据目录下的一个子目录。创建表示,Mysql 会在数据库子目录下创建一个和表同名的 `.frm` 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。** - -### 选择存储引擎 - -#### Mysql 内置的存储引擎 - -```shell -mysql> SHOW ENGINES; -+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ -| Engine | Support | Comment | Transactions | XA | Savepoints | -+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ -| FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL | -| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO | -| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES | -| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO | -| MyISAM | YES | MyISAM storage engine | NO | NO | NO | -| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO | -| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO | -| CSV | YES | CSV storage engine | NO | NO | NO | -| ARCHIVE | YES | Archive storage engine | NO | NO | NO | -+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ -9 rows in set (0.00 sec) -``` - -- **InnoDB** - Mysql 的默认事务型存储引擎,并且提供了行级锁和外键的约束。性能不错且支持自动崩溃恢复。 -- **MyISAM** - Mysql 5.1 版本前的默认存储引擎。特性丰富但不支持事务,也不支持行级锁和外键,也没有崩溃恢复功能。 -- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。 -- **Memory** - 适合快速访问数据,且数据不会被修改,重启丢失也没有关系。 -- **NDB** - 用于 Mysql 集群场景。 - -#### 如何选择合适的存储引擎 - -大多数情况下,InnoDB 都是正确的选择,除非需要用到 InnoDB 不具备的特性。 - -如果应用需要选择 InnoDB 以外的存储引擎,可以考虑以下因素: - -- 事务:如果需要支持事务,InnoDB 是首选。如果不需要支持事务,且主要是 SELECT 和 INSERT 操作,MyISAM 是不错的选择。所以,如果 Mysql 部署方式为主备模式,并进行读写分离。那么可以这么做:主节点只支持写操作,默认引擎为 InnoDB;备节点只支持读操作,默认引擎为 MyISAM。 -- 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。所以,InnoDB 并发性能更高。 -- 外键:InnoDB 支持外键。 -- 备份:InnoDB 支持在线热备份。 -- 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 -- 其它特性:MyISAM 支持压缩表和空间数据索引。 - -#### 转换表的存储引擎 - -下面的语句可以将 mytable 表的引擎修改为 InnoDB - -```sql -ALTER TABLE mytable ENGINE = InnoDB -``` - -### MyISAM - -MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。 - -MyISAM 引擎使用 B+Tree 作为索引结构,**叶节点的 data 域存放的是数据记录的地址**。 - -MyISAM 提供了大量的特性,包括:全文索引、压缩表、空间函数等。但是,MyISAM 不支持事务和行级锁。并且 MyISAM 不支持崩溃后的安全恢复。 - -### InnoDB - -InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 - -然 InnoDB 也使用 B+Tree 作为索引结构,但具体实现方式却与 MyISAM 截然不同。MyISAM 索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而**在 InnoDB 中,表数据文件本身就是按 B+Tree 组织的一个索引结构**,这棵树的叶节点 data 域保存了完整的数据记录。这个**索引的 key 是数据表的主键**,因此**InnoDB 表数据文件本身就是主索引**。 - -InnoDB 采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别。其默认级别是可重复读(REPEATABLE READ),并且通过间隙锁(next-key locking)防止幻读。 - -InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 - -内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。 - -支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -## 数据类型 - -### 整型 - -`TINYINT`, `SMALLINT`, `MEDIUMINT`, `INT`, `BIGINT` 分别使用 `8`, `16`, `24`, `32`, `64` 位存储空间,一般情况下越小的列越好。 - -**`UNSIGNED` 表示不允许负值,大致可以使正数的上限提高一倍**。 - -`INT(11)` 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。 - -### 浮点型 - -`FLOAT` 和 `DOUBLE` 为浮点类型。 - -`DECIMAL` 类型主要用于精确计算,代价较高,应该尽量只在对小数进行精确计算时才使用 `DECIMAL` ——例如存储财务数据。数据量比较大的时候,可以使用 `BIGINT` 代替 `DECIMAL`。 - -`FLOAT`、`DOUBLE` 和 `DECIMAL` 都可以指定列宽,例如 `DECIMAL(18, 9)` 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。 - -### 字符串 - -主要有 `CHAR` 和 `VARCHAR` 两种类型,一种是定长的,一种是变长的。 - -**`VARCHAR` 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长**。当超出一个页所能容纳的大小时,就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。 - -`VARCHAR` 会保留字符串末尾的空格,而 `CHAR` 会删除。 - -### 时间和日期 - -MySQL 提供了两种相似的日期时间类型:`DATATIME` 和 `TIMESTAMP`。 - -#### DATATIME - -能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。 - -它与时区无关。 - -默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值,例如“2008-01-16 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。 - -#### TIMESTAMP - -和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。 - -它和时区有关,也就是说一个时间戳在不同的时区所代表的具体时间是不同的。 - -MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 UNIX 时间戳。 - -默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。 - -应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。 - -### BLOB 和 TEXT - -`BLOB` 和 `TEXT` 都是为了存储大的数据而设计,前者存储二进制数据,后者存储字符串数据。 - -不能对 `BLOB` 和 `TEXT` 类型的全部内容进行排序、索引。 - -### 枚举类型 - -大多数情况下没有使用枚举类型的必要,其中一个缺点是:枚举的字符串列表是固定的,添加和删除字符串(枚举选项)必须使用`ALTER TABLE`(如果只只是在列表末尾追加元素,不需要重建表)。 - -### 类型的选择 - -- 整数类型通常是标识列最好的选择,因为它们很快并且可以使用 `AUTO_INCREMENT`。 - -- `ENUM` 和 `SET` 类型通常是一个糟糕的选择,应尽量避免。 -- 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 - - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 - -## 索引 - -> 详见:[Mysql 索引](05.Mysql索引.md) - -## 锁 - -> 详见:[Mysql 锁](04.Mysql锁.md) - -## 事务 - -> 详见:[Mysql 事务](03.Mysql事务.md) - -## 性能优化 - -> 详见:[Mysql 性能优化](06.Mysql性能优化.md) - -## 复制 - -### 主从复制 - -Mysql 支持两种复制:基于行的复制和基于语句的复制。 - -这两种方式都是在主库上记录二进制日志,然后在从库重放日志的方式来实现异步的数据复制。这意味着:复制过程存在时延,这段时间内,主从数据可能不一致。 - -主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。 - -- **binlog 线程** :负责将主服务器上的数据更改写入二进制文件(binlog)中。 -- **I/O 线程** :负责从主服务器上读取二进制日志文件,并写入从服务器的中继日志中。 -- **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。 - -
- -
- -### 读写分离 - -主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 - -读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。 - -MySQL 读写分离能提高性能的原因在于: - -- 主从服务器负责各自的读和写,极大程度缓解了锁的争用; -- 从服务器可以配置 MyISAM 引擎,提升查询性能以及节约系统开销; -- 增加冗余,提高可用性。 - -
- -
- -## 参考资料 - -- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [20+ 条 MySQL 性能优化的最佳经验](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) -- [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases) -- [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx) - -## 传送门 - -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\346\236\266\346\236\204.md" similarity index 67% rename from "source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" rename to "source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\346\236\266\346\236\204.md" index d5019e2c9c..a52d9de056 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.MySQL\345\267\245\344\275\234\346\265\201.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/01.Mysql\346\236\266\346\236\204.md" @@ -1,6 +1,9 @@ --- -title: MySQL 工作流 +icon: logos:mysql +title: Mysql 架构 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202309242206810.png date: 2020-07-16 11:14:07 +order: 01 categories: - 数据库 - 关系型数据库 @@ -9,43 +12,44 @@ tags: - 数据库 - 关系型数据库 - Mysql + - 日志 + - binlog + - WAL permalink: /pages/8262aa/ --- -# MySQL 工作流 - -## 基础架构 +# Mysql 架构 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。 -**Server 层包括连接器、查询缓存、分析器、优化器、执行器等**,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 +**Server 层包括连接器、查询缓存、解析器、优化器、执行器等**,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 **存储引擎层负责数据的存储和提取**。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200227201908.jpg) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311111138178.png) -## 查询过程 +## Mysql 查询流程 SQL 语句在 MySQL 中是如何执行的? -MySQL 整个查询执行过程,总的来说分为 6 个步骤,分别对应 6 个组件: +MySQL 整个查询执行过程,总的来说分为 6 个步骤: -1. 连接器:客户端和 MySQL 服务器建立连接;连接器负责跟客户端建立连接、获取权限、维持和管理连接。 -2. MySQL 服务器首先检查查询缓存,如果命中缓存,则立刻返回结果。否则进入下一阶段。 -3. MySQL 服务器进行 SQL 分析:语法分析、词法分析。 -4. MySQL 服务器用优化器生成对应的执行计划。 -5. MySQL 服务器根据执行计划,调用存储引擎的 API 来执行查询。 -6. MySQL 服务器将结果返回给客户端,同时缓存查询结果。 +1. **连接器** - 客户端和 MySQL 服务器建立连接;连接器负责跟客户端建立连接、获取权限、维持和管理连接。 +2. **查询缓存** - MySQL 服务器首先检查查询缓存,如果命中缓存,则立刻返回结果。否则进入下一阶段。 +3. **分析器** - MySQL 服务器进行 SQL 解析:语法分析、词法分析。 +4. **优化器** - MySQL 服务器用优化器生成对应的执行计划。 +5. **执行器** - MySQL 服务器根据执行计划,调用存储引擎的 API 来执行查询。 +6. **返回结果** - MySQL 服务器将结果返回给客户端,同时缓存查询结果。 -### (一)连接器 +### 连接器 使用 MySQL 第一步自然是要连接数据库。**连接器负责跟客户端建立连接、获取权限、维持和管理连接**。 -MySQL 客户端/服务端通信是**半双工模式**:即任一时刻,要么是服务端向客户端发送数据,要么是客户端向服务器发送数据。客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置`max_allowed_packet`参数。但是需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常。 +MySQL 客户端/服务端通信是**半双工模式**:即任一时刻,要么是服务端向客户端发送数据,要么是客户端向服务器发送数据。客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置 `max_allowed_packet` 参数。但是需要注意的是,如果查询实在是太大,服务端会拒绝接收更多数据并抛出异常。 -MySQL 客户端连接命令:`mysql -h<主机> -P<端口> -u<用户名> -p<密码>`。如果没有显式指定密码,会要求输入密码才能访问。 +MySQL 客户端连接命令形式为:`mysql -h<主机> -P<端口> -u<用户名> -p<密码>`。如果没有显式指定密码,会要求输入密码才能访问。 -连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 `show processlist` 命令中看到它。客户端如果太长时间没动静,连接器就会自动将它断开。**客户端连接维持时间是由参数 `wait_timeout` 控制的,默认值是 8 小时**。如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: `Lost connection to MySQL server during query`。这时候如果你要继续,就需要重连,然后再执行请求了。 +连接完成后,如果没有后续的动作,这个连接就处于**空闲状态**。**可以执行 `show processlist` 命令查看当前有多少个客户端连接**。**客户端如果空闲太久,连接器就会自动将它断开**。客户端连接维持时间是由参数 `wait_timeout` 控制的,默认值是 8 小时。如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: `Lost connection to MySQL server during query`。这时候如果你要继续,就需要重连,然后再执行请求了。 建立连接的过程通常是比较复杂的,建议在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。为了在程序中提高数据库连接的服用了,一般会使用数据库连接池来维护管理。 @@ -56,7 +60,7 @@ MySQL 客户端连接命令:`mysql -h<主机> -P<端口> -u<用户名> -p<密 - **定期断开长连接**。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。 - 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 `mysql_reset_connection` 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。 -### (二)查询缓存 +### 查询缓存 > **不建议使用数据库缓存,因为往往弊大于利**。 @@ -76,14 +80,14 @@ select SQL_CACHE * from T where ID=10; > 注意:MySQL 8.0 版本直接将查询缓存的整块功能删掉了。 -### (三)语法分析 +### 分析器 如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。MySQL 通过关键字对 SQL 语句进行解析,并生成一颗对应的语法解析树。这个过程中,分析器主要通过语法规则来验证和解析。比如 SQL 中是否使用了错误的关键字或者关键字的顺序是否正确等等。预处理则会根据 MySQL 规则进一步检查解析树是否合法。比如检查要查询的数据表和数据列是否存在等等。 - 分析器先会先做“**词法分析**”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。 - 接下来,要做“**语法分析**”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。 -### (四)查询优化 +### 优化器 经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。 @@ -116,11 +120,11 @@ MySQL 的查询优化器是一个非常复杂的部件,它使用了非常多 随着 MySQL 的不断发展,优化器使用的优化策略也在不断的进化,这里仅仅介绍几个非常常用且容易理解的优化策略,其他的优化策略,大家自行查阅吧。 -### (五)查询执行引擎 +### 执行器 -在完成解析和优化阶段以后,MySQL 会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操作均是通过调用存储引擎实现的接口来完成,这些接口被称为`handler API`。查询过程中的每一张表由一个`handler`实例表示。实际上,MySQL 在查询优化阶段就为每一张表创建了一个`handler`实例,优化器可以根据这些实例的接口来获取表的相关信息,包括表的所有列名、索引统计信息等。存储引擎接口提供了非常丰富的功能,但其底层仅有几十个接口,这些接口像搭积木一样完成了一次查询的大部分操作。 +在完成解析和优化阶段以后,MySQL 会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操作均是通过调用存储引擎实现的接口来完成,这些接口被称为 `handler API`。查询过程中的每一张表由一个 `handler` 实例表示。实际上,MySQL 在查询优化阶段就为每一张表创建了一个 `handler` 实例,优化器可以根据这些实例的接口来获取表的相关信息,包括表的所有列名、索引统计信息等。存储引擎接口提供了非常丰富的功能,但其底层仅有几十个接口,这些接口像搭积木一样完成了一次查询的大部分操作。 -### (六)返回结果 +### 返回结果 查询过程的最后一个阶段就是将结果返回给客户端。即使查询不到数据,MySQL 仍然会返回这个查询的相关信息,比如该查询影响到的行数以及执行时间等等。 @@ -128,41 +132,37 @@ MySQL 的查询优化器是一个非常复杂的部件,它使用了非常多 结果集返回客户端是一个增量且逐步返回的过程。有可能 MySQL 在生成第一条结果时,就开始向客户端逐步返回结果集了。这样服务端就无须存储太多结果而消耗过多内存,也可以让客户端第一时间获得返回结果。需要注意的是,结果集中的每一行都会以一个满足 ① 中所描述的通信协议的数据包发送,再通过 TCP 协议进行传输,在传输过程中,可能对 MySQL 的数据包进行缓存然后批量发送。 -## 更新过程 +## Mysql 更新流程 MySQL 更新过程和 MySQL 查询过程类似,也会将流程走一遍。不一样的是:**更新流程还涉及两个重要的日志模块,:redo log(重做日志)和 binlog(归档日志)**。 ### redo log -**redo log 是 InnoDB 引擎特有的日志**。**redo log 即重做日志**。redo log 是物理日志,记录的是“在某个数据页上做了什么修改”。 - -**redo log 是基于 WAL 技术**。WAL 的全称是 **Write-Ahead Logging**,它的关键点就是**先写日志,再写磁盘**。具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。 +如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 采用了 WAL 技术(全程是 Write-Ahead Logging),它的关键点就是先写日志,再写磁盘。 -InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写。 +具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630180342.png) - -有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为**crash-safe**。 +InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。 -### bin log +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311111210060.png) -**bin log 即归档日志**。binlog 是逻辑日志,记录的是这个语句的原始逻辑。 +write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。 -binlog 是可以追加写入的,即写到一定大小后会切换到下一个,并不会覆盖以前的日志。 +write pos 和 checkpoint 之间的是还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。 -**binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用**。 +有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为**crash-safe**。 -`sync_binlog` 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 +### binlog -### redo log vs. bin log +redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。 -这两种日志有以下三点不同。 +redo log 和 binlog 的差异: -- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。 -- redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。 -- redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。 +1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。 +2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。 +3. redo log 是循环写的,空间固定会用完;binlog 是追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。 -有了对这两个日志的概念性理解,我们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。 +再来看一下:update 语句时的内部流程 1. 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。 2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。 @@ -170,24 +170,24 @@ binlog 是可以追加写入的,即写到一定大小后会切换到下一个 4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。 5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。 -这里我给出这个 update 语句的执行流程图,图中浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行的。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200714133806.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220720210120.png) ### 两阶段提交 -redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。为什么日志需要“两阶段提交”。 +为什么日志需要“两阶段提交” + +由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。 + +1. **先写 redo log 后写 binlog**。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。 -由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。 +- 但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。 +- 然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。 -- **先写 redo log 后写 binlog**。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。 - 但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。 - 然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。 -- **先写 binlog 后写 redo log**。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。 +2. **先写 binlog 后写 redo log**。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。 可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。 ## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.Mysql\345\255\230\345\202\250\345\274\225\346\223\216.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.Mysql\345\255\230\345\202\250\345\274\225\346\223\216.md" new file mode 100644 index 0000000000..74477f9e99 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/02.Mysql\345\255\230\345\202\250\345\274\225\346\223\216.md" @@ -0,0 +1,270 @@ +--- +icon: logos:mysql +title: Mysql 存储引擎 +date: 2020-07-13 10:08:37 +order: 02 +categories: + - 数据库`` + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 存储引擎 + - InnoDB +permalink: /pages/5fe0f3/ +--- + +# Mysql 存储引擎 + +在文件系统中,Mysql 将每个数据库(也可以成为 schema)保存为数据目录下的一个子目录。创建表示,Mysql 会在数据库子目录下创建一个和表同名的 `.frm` 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义,大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。** + +MySQL 的存储引擎采用了插件的形式,每个存储引擎都面向一种特定的数据库应用环境。同时开源的 MySQL 还允许开发人员设置自己的存储引擎,下面是一些常见的存储引擎: + +- InnoDB 存储引擎:它是 MySQL 5.5 版本之后默认的存储引擎,最大的特点是支持事务、行级锁定、外键约束等。 +- MyISAM 存储引擎:在 MySQL 5.5 版本之前是默认的存储引擎,不支持事务,也不支持外键,最大的特点是速度快,占用资源少。 +- Memory 存储引擎:使用系统内存作为存储介质,以便得到更快的响应速度。不过如果 mysqld 进程崩溃,则会导致所有的数据丢失,因此我们只有当数据是临时的情况下才使用 Memory 存储引擎。 +- NDB 存储引擎:也叫做 NDB Cluster 存储引擎,主要用于 MySQL Cluster 分布式集群环境,类似于 Oracle 的 RAC 集群。 +- Archive 存储引擎:它有很好的压缩机制,用于文件归档,在请求写入时会进行压缩,所以也经常用来做仓库。 + +## 存储引擎相关操作 + +### 查看存储引擎命令 + +```sql +# 查看支持的存储引擎 +SHOW ENGINES; + +# 查看默认的存储引擎 +SHOW VARIABLES LIKE 'storage_engine'; + +# 查看某表所使用的存储引擎 +SHOW CREATE TABLE `table_name`; + +# 查看某数据库中的某表所使用的存储引擎 +SHOW TABLE STATUS LIKE 'table_name'; +SHOW TABLE STATUS FROM `database_name` WHERE `name` = "table_name"; +``` + +### 设置存储引擎命令 + +```sql +# 建表时指定存储引擎,如果不显示指定,默认是 INNODB +CREATE TABLE t1 (i INT) ENGINE = INNODB; +CREATE TABLE t2 (i INT) ENGINE = CSV; +CREATE TABLE t3 (i INT) ENGINE = MEMORY; + +# 修改存储引擎 +ALTER TABLE t ENGINE = InnoDB; + +# 修改默认存储引擎,也可以在配置文件 my.cnf 中修改默认引擎 +SET default_storage_engine=NDBCLUSTER; +``` + +默认情况下,每当 `CREATE TABLE` 或 `ALTER TABLE` 不能使用默认存储引擎时,都会生成一个警告。为了防止在所需的引擎不可用时出现令人困惑的意外行为,可以启用 `NO_ENGINE_SUBSTITUTION SQL` 模式。如果所需的引擎不可用,则此设置将产生错误而不是警告,并且不会创建或更改表 + +## Mysql 存储引擎简介 + +### Mysql 内置的存储引擎 + +- **InnoDB** - InnoDB 是 MySQL 5.5 版本以后的默认存储引擎。并且提供了行级锁和外键的约束。性能不错且支持自动崩溃恢复。 +- **MyISAM** - MyISAM 是 MySQL 5.5 版本以后的默认存储引擎。特性丰富但不支持事务,也不支持行级锁和外键,也没有崩溃恢复功能。 +- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。 +- **Memory** - 数据存储在内存,以便得到更快的响应速度。不过如果 mysqld 进程崩溃,则会导致所有的数据丢失。 +- **NDB** - 也叫做 NDB Cluster 存储引擎,主要用于 MySQL Cluster 分布式集群环境,类似于 Oracle 的 RAC 集群。 +- **Archieve** - Archieve 存储引擎非常适合用于归档数据。 + - Archieve 存储引擎只支持 `INSERT` 和 `SELECT` 操作。 + - Archieve 存储引擎采用 zlib 算法压缩数据,压缩比可达到 1: 10。 + +### 如何选择合适的存储引擎 + +大多数情况下,InnoDB 都是正确的选择,除非需要用到 InnoDB 不具备的特性。 + +如果应用需要选择 InnoDB 以外的存储引擎,可以考虑以下因素: + +- 事务:如果业务场景是 OLTP,则 InnoDB 是首选存储引擎。如果不需要支持事务,且主要是 SELECT 和 INSERT 操作,MyISAM 是不错的选择。所以,如果 Mysql 部署方式为主备模式,并进行读写分离。那么可以这么做:主节点只支持写操作,默认引擎为 InnoDB;备节点只支持读操作,默认引擎为 MyISAM。 +- 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。所以,InnoDB 并发性能更高。 +- 外键:InnoDB 支持外键。 +- 备份:InnoDB 支持在线热备份。 +- 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 +- 其它特性:MyISAM 支持压缩表和空间数据索引。 + +## InnoDB 简介 + +InnoDB 是 MySQL 5.5 版本以后的默认存储引擎。只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 + +InnoDB 也使用 B+Tree 作为索引结构,但具体实现方式却与 MyISAM 截然不同。MyISAM 索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而**在 InnoDB 中,表数据文件本身就是按 B+Tree 组织的一个索引结构**,这棵树的叶节点 data 域保存了完整的数据记录。这个**索引的 key 是数据表的主键**,因此**InnoDB 表数据文件本身就是主索引**。 + +InnoDB 采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别。其默认级别是可重复读(REPEATABLE READ),并且通过间隙锁(next-key locking)防止幻读。 + +InnoDB 是基于聚簇索引建立的,与其他存储引擎有很大不同。在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 + +内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。 + +支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 + +InnoDB 物理文件结构为: + +- `.frm` 文件:与表相关的元数据信息都存放在 frm 文件,包括表结构的定义信息等。 + +- `.ibd` 文件或 `.ibdata` 文件: 这两种文件都是存放 InnoDB 数据的文件,之所以有两种文件形式存放 InnoDB 的数据,是因为 InnoDB 的数据存储方式能够通过配置来决定是使用**共享表空间**存放存储数据,还是用**独享表空间**存放存储数据。 + + 独享表空间存储方式使用`.ibd`文件,并且每个表一个`.ibd`文件 共享表空间存储方式使用`.ibdata`文件,所有表共同使用一个`.ibdata`文件(或多个,可自己配置) + +## InnoDB 存储架构 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311070640589.png) + +InnoDB 存储架构分为内存结构和磁盘结构。 + +InnoDB 内存结构的核心组件有: + +- Buffer Pool +- Change Buffer +- Adaptive Hash Index +- Log Buffer + +InnoDB 磁盘结构的核心组件有: + +- Tablespace +- Doublewrite Buffer +- redo log +- undo log + +## InnoDB 表空间 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311070708733.png) + +### 行(row) + +数据库表中的记录都是按行(row)进行存放的,每行记录根据不同的行格式,有不同的存储结构。 + +后面我们详细介绍 InnoDB 存储引擎的行格式,也是本文重点介绍的内容。 + +### 页(page) + +记录是按照行来存储的,但是数据库的读取并不以「行」为单位,否则一次读取(也就是一次 I/O 操作)只能处理一行数据,效率会非常低。 + +因此,**InnoDB 的数据是按「页」为单位来读写的**,也就是说,当需要读一条记录的时候,并不是将这个行记录从磁盘读出来,而是以页为单位,将其整体读入内存。 + +**默认每个页的大小为 16KB**,也就是最多能保证 16KB 的连续存储空间。 + +页是 InnoDB 存储引擎磁盘管理的最小单元,意味着数据库每次读写都是以 16KB 为单位的,一次最少从磁盘中读取 16K 的内容到内存中,一次最少把内存中的 16K 内容刷新到磁盘中。 + +页的类型有很多,常见的有数据页、undo 日志页、溢出页等等。数据表中的行记录是用「数据页」来管理的,数据页的结构这里我就不讲细说了,之前文章有说过,感兴趣的可以去看这篇文章:[换一个角度看 B+ 树(opens new window)](https://xiaolincoding.com/mysql/index/page.html) + +总之知道表中的记录存储在「数据页」里面就行。 + +### 区(extent) + +我们知道 InnoDB 存储引擎是用 B+ 树来组织数据的。 + +B+ 树中每一层都是通过双向链表连接起来的,如果是以页为单位来分配存储空间,那么链表中相邻的两个页之间的物理位置并不是连续的,可能离得非常远,那么磁盘查询时就会有大量的随机 I/O,随机 I/O 是非常慢的。 + +解决这个问题也很简单,就是让链表中相邻的页的物理位置也相邻,这样就可以使用顺序 I/O 了,那么在范围查询(扫描叶子节点)的时候性能就会很高。 + +那具体怎么解决呢? + +**在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区(extent)为单位分配。每个区的大小为 1MB,对于 16KB 的页来说,连续的 64 个页会被划为一个区,这样就使得链表中相邻的页的物理位置也相邻,就能使用顺序 I/O 了**。 + +### 段(segment) + +表空间是由各个段(segment)组成的,段是由多个区(extent)组成的。段一般分为数据段、索引段和回滚段等。 + +- 索引段:存放 B + 树的非叶子节点的区的集合; +- 数据段:存放 B + 树的叶子节点的区的集合; +- 回滚段:存放的是回滚数据的区的集合。 + +好了,终于说完表空间的结构了。接下来,就具体讲一下 InnoDB 的行格式了。 + +之所以要绕一大圈才讲行记录的格式,主要是想让大家知道行记录是存储在哪个文件,以及行记录在这个表空间文件中的哪个区域,有一个从上往下切入的视角,这样理解起来不会觉得很抽象。 + +## InnoDB 内存结构 + +### Buffer Pool + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311070641009.png) + +Buffer Pool 用于加速数据的访问和修改,通过将热点数据缓存在内存的方法,最大限度地减少磁盘 IO,加速热点数据的读和写。 + +Buffer Pool 中数据**以页为存储单位**,其实现数据结构是**以页为单位的单链表**。 + +由于内存的空间限制,Buffer Pool 仅能容纳最热点的数据。Buffer Pool 使用最近最少使用算法(Least Recent Used,LRU)算法淘汰非热点数据页。 + +依据时间局部性原理与空间局部性原理,Buffer Pool 在存储当前活动数据页的时候,会以预读 Read-ahead 的方式缓存目标数据页临近的数据页。 + +预读机制带来预读失败的问题,InnoDB **采用分代机制解决预读失败问题**:将 Buffer Pool 分为 New SubList 和 Old SubList 两部分,将最新读取的数据页置于 Old SubList 头部,Old SubList 中的数据再次被访问到才会置于 New SubList 头部;预读失败的冷数据将更快地从 Old SubList 中淘汰,而不会影响到 New SubList 中原有的热数据。 + +预读失败问题可以引申到缓冲池污染问题,InnoDB **采用时间窗口(Time Window)机制解决缓冲池污染问题**:对于 Old SubList 中的数据页,必须在 Old SubList 中停留到达指定时间之后再次被访问到,才能转移到 New SubList 中,默认窗口大小是 1s。 + +对于 Buffer Pool 中数据的查询,InnoDB 直接读取返回;对于 Buffer Pool 中数据的修改,InnoDB 直接在 Buffer Pool 中修改,并将修改写入 redo Log 中,当数据页被 LRU 算法淘汰时写入磁盘,若持久化前系统崩溃,则在重启后使用 redo Log 进行恢复。 + +### Change Buffer + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311070641668.png) + +Change Buffer 用于加速非热点数据中二级索引的写入操作。由于二级索引数据的不连续性,导致修改二级索引时需要进行频繁的磁盘 IO 消耗大量性能,Change Buffer 缓冲对二级索引的修改操作,同时将写操作录入 redo log 中,在缓冲到一定量或系统较空闲时进行 `ibuf merge` 操作将修改写入磁盘中。Change Buffer 在系统表空间中有相应的持久化区域。 + +Change Buffer 大小默认占 Buffer Pool 的 25%,在引擎启动时便初始化完成。其物理结构为一棵名为 `ibuf` 的 B Tree。Change Buffer 的使用条件为: + +- InnoDB 开启 `innodb_change_buffering`,且该表当前没有 `flush` 操作。 +- 仅对二级索引树的叶子节点进行修改,且该索引页不在 Buffer Pool 中。 +- 对于 Unique 二级索引,仅删除操作可以缓冲。 + +其 `ibuf merge` 时机为: + +- 用户使用该二级索引进行查询时。 +- 缓存插入操作时,预估到 page 空间不足可能导致索引页分裂时。 +- 本次缓存操作将导致 ibuf btree 页分裂,且分类后 Change Buffer 大小将超出限制时。 +- master 线程发起 `merge` 命令时。 +- 用户对该表进行 `flush` 操作时。 + +### Adaptive Hash Index + +自适应哈希索引(Adaptive Hash Index)用于实现对于热数据页的一次查询。使用聚簇索引进行数据页定位的时候需要根据索引树的高度从根节点走到叶子节点,通常需要 3 到 4 次查询才能定位数据。InnoDB 根据对索引使用情况的分析和索引字段的分析,通过自调优 Self-tuning 的方式为索引页建立或者删除哈希索引。 + +AHI 所作用的目标是频繁查询的数据页和索引页,而由于数据页是聚簇索引的一部分,因此 AHI 是建立在索引之上的索引,**对于二级索引,若命中 AHI,则将直接从 AHI 获取二级索引页的记录指针,再根据主键沿着聚簇索引查找数据;若聚簇索引查询同样命中 AHI,则直接返回目标数据页的记录指针,此时就可以根据记录指针直接定位数据页**。 + +AHI 的大小为 Buffer Pool 的 1/64,再 MySql 5.7 之后支持分区,以减少对于全局 AHI 锁的竞争,默认分区数为 8。 + +### Log Buffer + +Log Buffer 是用于缓冲待写入磁盘日志文件的数据。InnoDB 的所有修改操作都会被写入 redo log、undo log 等日志文件,如果每次都直接写入磁盘,会引发大量 IO。Log Buffer 正是针对此进行了优化:先将修改操作缓冲于此内存区域,然后定期批量 刷新到磁盘。 + +日志缓冲区大小可以由配置 [`innodb_log_buffer_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_log_buffer_size) 控制,默认大小为 16MB。 + +## MyISAM + +MyISAM 是 MySQL 5.5 版本以前的默认存储引擎。 + +MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。 + +MyISAM 引擎使用 B+Tree 作为索引结构,**叶节点的 data 域存放的是数据记录的地址**。 + +MyISAM 提供了大量的特性,包括:全文索引、压缩表、空间函数等。但是,MyISAM 不支持事务和行级锁。并且 MyISAM 不支持崩溃后的安全恢复。 + +MyISAM 物理文件结构为: + +- `.frm`文件:与表相关的元数据信息都存放在 frm 文件,包括表结构的定义信息等。 +- `.MYD` (`MYData`) 文件:MyISAM 存储引擎专用,用于存储 MyISAM 表的数据。 +- `.MYI` (`MYIndex`)文件:MyISAM 存储引擎专用,用于存储 MyISAM 表的索引相关信息。 + +## InnoDB vs. MyISAM + +InnoDB 和 MyISAM 的对比: + +| 对比项 | MyISAM | InnoDB | +| ------ | --------------------------------------------- | -------------------------------- | +| 主外键 | 不支持 | 支持 | +| 事务 | 不支持 | 支持 | +| 锁 | 支持表级锁 | 支持表级锁、行级锁 | +| 索引 | 采用非聚簇索引 | 主键采用聚簇索引,以提高 IO 效率 | +| 表空间 | 小 | 大 | +| 关注点 | 性能 | 事务 | +| 计数器 | 维护了计数器,`SELECT COUNT(*)` 效率为 `O(1)` | 没有维护计数器,需要全表扫描 | + +## 参考资料 + +- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" deleted file mode 100644 index e0c2df9ea6..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\344\272\213\345\212\241.md" +++ /dev/null @@ -1,375 +0,0 @@ ---- -title: Mysql 事务 -date: 2020-06-03 19:32:09 -categories: - - 数据库 - - 关系型数据库 - - Mysql -tags: - - 数据库 - - 关系型数据库 - - Mysql - - 事务 -permalink: /pages/00b04d/ ---- - -# Mysql 事务 - -> 不是所有的 Mysql 存储引擎都实现了事务处理。支持事务的存储引擎有:`InnoDB` 和 `NDB Cluster`。不支持事务的存储引擎,代表有:`MyISAM`。 -> -> 用户可以根据业务是否需要事务处理(事务处理可以保证数据安全,但会增加系统开销),选择合适的存储引擎。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220721072721.png) - -## 事务简介 - -> 事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**。进一步说,事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库事务.png) - -**事务就是一组原子性的 SQL 语句**。具体来说,事务指的是满足 ACID 特性的一组操作。 - -**事务内的 SQL 语句,要么全执行成功,要么全执行失败**。 - -**通过加锁的方式,可以实现不同的事务隔离机制**。 - -想象一下,如果没有事务,在并发环境下,就可能出现丢失修改的问题。 - -T1 和 T2 两个线程都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-丢失修改.png) - -## 事务用法 - -### 事务处理指令 - -Mysql 中,使用 `START TRANSACTION` 语句开始一个事务;使用 `COMMIT` 语句提交所有的修改;使用 `ROLLBACK` 语句撤销所有的修改。不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。 - -- `START TRANSACTION` - 指令用于标记事务的起始点。 -- `SAVEPOINT` - 指令用于创建保留点。 -- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。 -- `COMMIT` - 提交事务。 - -事务处理示例: - -(1)创建一张示例表 - -```sql --- 撤销表 user -DROP TABLE IF EXISTS user; - --- 创建表 user -CREATE TABLE user ( - id int(10) unsigned NOT NULL COMMENT 'Id', - username varchar(64) NOT NULL DEFAULT 'default' COMMENT '用户名', - password varchar(64) NOT NULL DEFAULT 'default' COMMENT '密码', - email varchar(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' -) COMMENT='用户表'; -``` - -(2)执行事务操作 - -```sql --- 开始事务 -START TRANSACTION; - --- 插入操作 A -INSERT INTO `user` -VALUES (1, 'root1', 'root1', 'xxxx@163.com'); - --- 创建保留点 updateA -SAVEPOINT updateA; - --- 插入操作 B -INSERT INTO `user` -VALUES (2, 'root2', 'root2', 'xxxx@163.com'); - --- 回滚到保留点 updateA -ROLLBACK TO updateA; - --- 提交事务,只有操作 A 生效 -COMMIT; -``` - -(3)执行结果 - -```sql -SELECT * FROM user; -``` - -结果: - -``` -1 root1 root1 xxxx@163.com -``` - -### AUTOCOMMIT - -**MySQL 默认采用隐式提交策略(`autocommit`)**。每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 - -通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 - -```sql --- 查看 AUTOCOMMIT -SHOW VARIABLES LIKE 'AUTOCOMMIT'; - --- 关闭 AUTOCOMMIT -SET autocommit = 0; - --- 开启 AUTOCOMMIT -SET autocommit = 1; -``` - -## ACID - -ACID 是数据库事务正确执行的四个基本要素。 - -- **原子性(Atomicity)** - - 事务被视为不可分割的最小单元,事务中的所有操作要么全部提交成功,要么全部失败回滚。 - - 回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 -- **一致性(Consistency)** - - 数据库在事务执行前后都保持一致性状态。 - - 在一致性状态下,所有事务对一个数据的读取结果都是相同的。 -- **隔离性(Isolation)** - - 一个事务所做的修改在最终提交以前,对其它事务是不可见的。 -- **持久性(Durability)** - - 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 - - 可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 - -**一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。** - -- 只有满足一致性,事务的执行结果才是正确的。 -- 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。 -- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 -- 事务满足持久化是为了能应对系统崩溃的情况。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) - -> MySQL 默认采用自动提交模式(`AUTO COMMIT`)。也就是说,如果不显式使用 `START TRANSACTION` 语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。 - -## 事务隔离级别 - -### 事务隔离简介 - -在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题: - -- **丢失修改** -- **脏读** -- **不可重复读** -- **幻读** - -在 SQL 标准中,定义了四种事务隔离级别(级别由低到高): - -- **读未提交** -- **读提交** -- **可重复读** -- **串行化** - -Mysql 中查看和设置事务隔离级别: - -```sql --- 查看事务隔离级别 -SHOW VARIABLES LIKE 'transaction_isolation'; - --- 设置事务隔离级别为 READ UNCOMMITTED -SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - --- 设置事务隔离级别为 READ COMMITTED -SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; - --- 设置事务隔离级别为 REPEATABLE READ -SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; - --- 设置事务隔离级别为 SERIALIZABLE -SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; -``` - -### 读未提交 - -**`读未提交(read uncommitted)` 是指:事务中的修改,即使没有提交,对其它事务也是可见的**。 - -读未提交的问题:事务可以读取未提交的数据,也被称为 **脏读(Dirty Read)**。 - -T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-脏数据.png) - -### 读提交 - -**`读提交(read committed)` 是指:事务提交后,其他事务才能看到它的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。读提交解决了脏读的问题。 - -读提交是大多数数据库的默认事务隔离级别。 - -读提交有时也叫不可重复读,它的问题是:执行两次相同的查询,得到的结果可能不一致。 - -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-不可重复读.png) - -### 可重复读 - -**`可重复读(REPEATABLE READ)` 是指:保证在同一个事务中多次读取同样数据的结果是一样的**。可重复读解决了不可重复读问题。 - -可重复读是 Mysql 的默认事务隔离级别。 - -可重复读的问题:事务 T1 读取某个范围内的记录时,事务 T2 在该范围内插入了新的记录,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同,即为 **幻读(Phantom Read)**。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库并发一致性-幻读.png) - -### 串行化 - -**`串行化(SERIALIXABLE)` 是指:强制事务串行执行,对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁**。 - -强制事务串行执行,则避免了所有的并发问题。串行化策略会在读取的每一行数据上都加锁,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。 - -### 隔离级别小结 - -- **`读未提交(READ UNCOMMITTED)`** - 事务中的修改,即使没有提交,对其它事务也是可见的。 -- **`读提交(READ COMMITTED)`** - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 -- **`重复读(REPEATABLE READ)`** - 保证在同一个事务中多次读取同样数据的结果是一样的。 -- **`串行化(SERIALIXABLE)`** - 对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁。 - -数据库隔离级别解决的问题: - -| 隔离级别 | 丢失修改 | 脏读 | 不可重复读 | 幻读 | -| :------: | :------: | :--: | :--------: | :--: | -| 读未提交 | ✔️ | ❌ | ❌ | ❌ | -| 读提交 | ✔️ | ✔️ | ❌ | ❌ | -| 可重复读 | ✔️ | ✔️ | ✔️ | ❌ | -| 可串行化 | ✔️ | ✔️ | ✔️ | ✔️ | - -## 死锁 - -**死锁是指两个或多个事务竞争同一资源,并请求锁定对方占用的资源,从而导致恶性循环的现象**。 - -产生死锁的场景: - -- 当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。 - -- 多个事务同时锁定同一个资源时,也会产生死锁。 - -### 死锁的原因 - -行锁的具体实现算法有三种:record lock、gap lock 以及 next-key lock。record lock 是专门对索引项加锁;gap lock 是对索引项之间的间隙加锁;next-key lock 则是前面两种的组合,对索引项以其之间的间隙加锁。 - -只在可重复读或以上隔离级别下的特定操作才会取得 gap lock 或 next-key lock,在 Select、Update 和 Delete 时,除了基于唯一索引的查询之外,其它索引查询时都会获取 gap lock 或 next-key lock,即锁住其扫描的范围。主键索引也属于唯一索引,所以主键索引是不会使用 gap lock 或 next-key lock。 - -在 MySQL 中,gap lock 默认是开启的,即 innodb_locks_unsafe_for_binlog 参数值是 disable 的,且 MySQL 中默认的是 RR 事务隔离级别。 - -当我们执行以下查询 SQL 时,由于 order_no 列为非唯一索引,此时又是 RR 事务隔离级别,所以 SELECT 的加锁类型为 gap lock,这里的 gap 范围是 (4,+∞)。 - -> SELECT id FROM `demo`.`order_record` where `order_no` = 4 for update; - -执行查询 SQL 语句获取的 gap lock 并不会导致阻塞,而当我们执行以下插入 SQL 时,会在插入间隙上再次获取插入意向锁。插入意向锁其实也是一种 gap 锁,它与 gap lock 是冲突的,所以当其它事务持有该间隙的 gap lock 时,需要等待其它事务释放 gap lock 之后,才能获取到插入意向锁。 - -以上事务 A 和事务 B 都持有间隙 (4,+∞)的 gap 锁,而接下来的插入操作为了获取到插入意向锁,都在等待对方事务的 gap 锁释放,于是就造成了循环等待,导致死锁。 - -> INSERT INTO `demo`.`order_record`(`order_no`, `status`, `create_date`) VALUES (5, 1, ‘2019-07-13 10:57:03’); - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630153139.png) - -**另一个死锁场景** - -InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引。如果使用辅助索引来更新数据库,就需要使用聚簇索引来更新数据库字段。如果两个更新事务使用了不同的辅助索引,或一个使用了辅助索引,一个使用了聚簇索引,就都有可能导致锁资源的循环等待。由于本身两个事务是互斥,也就构成了以上死锁的四个必要条件了。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630154606.png) - -出现死锁的步骤: - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630154619.png) - -综上可知,在更新操作时,我们应该尽量使用主键来更新表字段,这样可以有效避免一些不必要的死锁发生。 - -### 避免死锁 - -预防死锁的注意事项: - -- 在编程中尽量按照固定的顺序来处理数据库记录,假设有两个更新操作,分别更新两条相同的记录,但更新顺序不一样,有可能导致死锁; -- 在允许幻读和不可重复读的情况下,尽量使用 RC 事务隔离级别,可以避免 gap lock 导致的死锁问题; -- 更新表时,**尽量使用主键更新**; -- 避免长事务,**尽量将长事务拆解**,可以降低与其它事务发生冲突的概率; -- **设置合理的锁等待超时参数**,我们可以通过 `innodb_lock_wait_timeout` 设置合理的等待超时阈值,特别是在一些高并发的业务中,我们可以尽量将该值设置得小一些,避免大量事务等待,占用系统资源,造成严重的性能开销。 - -另外,我们还可以将 order_no 列设置为唯一索引列。虽然不能防止幻读,但我们可以利用它的唯一性来保证订单记录不重复创建,这种方式唯一的缺点就是当遇到重复创建订单时会抛出异常。 - -我们还可以使用其它的方式来代替数据库实现幂等性校验。例如,使用 Redis 以及 ZooKeeper 来实现,运行效率比数据库更佳。 - -### 解决死锁 - -当出现死锁以后,有两种策略: - -- 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 `innodb_lock_wait_timeout` 来设置。 -- 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 `innodb_deadlock_detect` 设置为 on,表示开启这个逻辑。 - -在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。 - -但是,我们又不可能直接把这个时间设置成一个很小的值,比如 1s。这样当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。 - -所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 `innodb_deadlock_detect` 的默认值本身就是 on。为了解决死锁问题,不同数据库实现了各自的死锁检测和超时机制。InnoDB 的处理策略是:**将持有最少行级排它锁的事务进行回滚**。 - -主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。你可以想象一下这个过程:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。 - -## 分布式事务 - -在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为 **本地事务**。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 - -**分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性。** - -分布式事务的常见方案如下: - -- **两阶段提交(2PC)** - 将事务的提交过程分为两个阶段来进行处理:准备阶段和提交阶段。参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。 -- **三阶段提交(3PC)** - 与二阶段提交不同的是,引入超时机制。同时在协调者和参与者中都引入超时机制。将二阶段的准备阶段拆分为 2 个阶段,插入了一个 preCommit 阶段,使得原先在二阶段提交中,参与者在准备之后,由于协调者发生崩溃或错误,而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题得以解决。 -- **补偿事务(TCC)** - - **Try** - 操作作为一阶段,负责资源的检查和预留。 - - **Confirm** - 操作作为二阶段提交操作,执行真正的业务。 - - **Cancel** - 是预留资源的取消。 -- **本地消息表** - 在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。 -- **MQ 事务** - 基于 MQ 的分布式事务方案其实是对本地消息表的封装。 -- **SAGA** - Saga 事务核心思想是将长事务拆分为多个本地短事务,由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。 - -分布式事务方案分析: - -- 2PC/3PC 依赖于数据库,能够很好的提供强一致性和强事务性,但相对来说延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。 -- TCC 适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。 -- 本地消息表/MQ 事务 都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。 -- Saga 事务 由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。 Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。 - -> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://dunwu.github.io/blog/pages/e1881c/) - -## 事务最佳实践 - -高并发场景下的事务到底该如何调优? - -### 尽量使用低级别事务隔离 - -结合业务场景,尽量使用低级别事务隔离 - -### 避免行锁升级表锁 - -在 InnoDB 中,行锁是通过索引实现的,如果不通过索引条件检索数据,行锁将会升级到表锁。我们知道,表锁是会严重影响到整张表的操作性能的,所以应该尽力避免。 - -### 缩小事务范围 - -有时候,数据库并发访问量太大,会出现以下异常: - -``` -MySQLQueryInterruptedException: Query execution was interrupted -``` - -高并发时对一条记录进行更新的情况下,由于更新记录所在的事务还可能存在其他操作,导致一个事务比较长,当有大量请求进入时,就可能导致一些请求同时进入到事务中。 - -又因为锁的竞争是不公平的,当多个事务同时对一条记录进行更新时,极端情况下,一个更新操作进去排队系统后,可能会一直拿不到锁,最后因超时被系统打断踢出。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200630112600.png) - -如上图中的操作,虽然都是在一个事务中,但锁的申请在不同时间,只有当其他操作都执行完,才会释放所有锁。因为扣除库存是更新操作,属于行锁,这将会影响到其他操作该数据的事务,所以我们应该尽量避免长时间地持有该锁,尽快释放该锁。又因为先新建订单和先扣除库存都不会影响业务,所以我们可以将扣除库存操作放到最后,也就是使用执行顺序 1,以此尽量减小锁的持有时间。 - -**在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。** - -知道了这个设定,对我们使用事务有什么帮助呢?那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 - -## 参考资料 - -- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) -- [ShardingSphere 分布式事务](https://shardingsphere.apache.org/document/current/cn/features/transaction/) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\347\264\242\345\274\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\347\264\242\345\274\225.md" new file mode 100644 index 0000000000..3c1a7d6fc5 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/03.Mysql\347\264\242\345\274\225.md" @@ -0,0 +1,516 @@ +--- +icon: logos:mysql +title: Mysql 索引 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202310162333557.png +date: 2020-07-16 11:14:07 +order: 03 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 索引 +permalink: /pages/fcb19c/ +--- + +# Mysql 索引 + +> 索引是提高 MySQL 查询性能的一个重要途径,但过多的索引可能会导致过高的磁盘使用率以及过高的内存占用,从而影响应用程序的整体性能。应当尽量避免事后才想起添加索引,因为事后可能需要监控大量的 SQL 才能定位到问题所在,而且添加索引的时间肯定是远大于初始添加索引所需要的时间,可见索引的添加也是非常有技术含量的。 +> +> 接下来将向你展示一系列创建高性能索引的策略,以及每条策略其背后的工作原理。但在此之前,先了解与索引相关的一些算法和数据结构,将有助于更好的理解后文的内容。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310162333557.png) + +## 索引简介 + +**“索引”是数据库为了提高查找效率的一种数据结构**。 + +日常生活中,我们可以通过检索目录,来快速定位书本中的内容。索引和数据表,就好比目录和书,想要高效查询数据表,索引至关重要。在数据量小且负载较低时,不恰当的索引对于性能的影响可能还不明显;但随着数据量逐渐增大,性能则会急剧下降。因此,**设置合理的索引是数据库查询性能优化的最有效手段**。 + +### 索引的优缺点 + +B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用来做 `ORDER BY` 和 `GROUP BY` 操作。因为数据是有序的,所以 B 树也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。 + +✔️️️️ 索引的优点: + +- **索引大大减少了服务器需要扫描的数据量**,从而加快检索速度。 +- **索引可以帮助服务器避免排序和临时表**。 +- **索引可以将随机 I/O 变为顺序 I/O**。 +- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。**使用索引可以减少访问的行数,从而减少锁的竞争,提高并发**。 +- 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。 + +❌ 索引的缺点: + +- **创建和维护索引要耗费时间**,这会随着数据量的增加而增加。 +- **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 +- **写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低**。 + +基于以上,可以归纳出索引的基本使用规则: + +- 索引不是越多越好,不要为所有列都创建索引 +- 要尽量避免冗余和重复索引 +- 要考虑删除未使用的索引 +- 尽量的扩展索引,不要新建索引 +- 频繁作为 `WHERE` 过滤条件的列应该考虑添加索引 + +### 何时使用索引 + +✔️️️️ 什么情况**适用**索引? + +- **字段的数值有唯一性的限制**,如用户名。 +- **频繁作为 `WHERE` 条件或 `JOIN` 条件的字段,尤其在数据表大的情况下** +- **频繁用于 `GROUP BY` 或 `ORDER BY` 的字段**。将该字段作为索引,查询时就无需再排序了,因为 B+ 树 +- **DISTINCT 字段需要创建索引**。 + +❌ 什么情况**不适用**索引? + +- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` ),也就意味着需要更新索引。 +- **很少作为 `WHERE` 条件或 `JOIN` 条件的字段**,也就意味着索引会经常无法命中,没有意义,还增加空间开销。 +- **非常小的表**,对于非常小的表,大部分情况下简单的全表扫描更高效。 +- **特大型的表**,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 + +## 索引的数据结构 + +在 Mysql 中,**索引是在存储引擎层而不是服务器层实现的**,所以,并没有统一的索引标准。不同存储引擎的索引的数据结构也不相同。下面是 Mysql 常用存储引擎对一些主要索引数据结构的支持: + +| 索引数据结构/存储引擎 | InnoDB 引擎 | MyISAM 引擎 | Memory 引擎 | +| --------------------- | ----------- | ----------- | ----------- | +| **B+Tree 索引** | ✔️️️️️ | ✔️️️️️ | ✔️️️️️ | +| **Hash 索引** | ❌ | ❌ | ✔️️️️️ | +| **Full Text 索引** | ✔️️️️️ | ✔️️️️️ | ❌ | + +下面,我们将逐一探讨各种可能作为索引的数据结构,了解其特性、利弊、应用场景。相信通过这样的对比,可以让读者更加明确 Mysql 中为什么选择某些数据结构作为索引,而放弃了另外一些数据结构,依据是什么。 + +### 有序数组 + +**“数组”用连续的内存空间来存储数据,并且支持随机访问**。 + +有序数组可以使用二分查找法,其时间复杂度为 `O(log n)`,无论是等值查询还是范围查询,都非常高效。 + +但有序数组有两个重要限制: + +- **数组的空间大小固定**,如果要扩容只能采用复制数组的方式,比较低效。 +- **插入、删除操作开销较大**,时间复杂度为 `O(n)` (要保证数组有序)。 + +这意味着,如果直接使用有序数组作为索引,为了保证数组有序,其更新操作代价高昂。正因为如此,几乎没有数据库会采用有序数组作为索引。 + +### 哈希索引 + +哈希表是一种以键 - 值(key-value)对形式存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。 + +**“哈希表”使用哈希函数组织数据,以支持快速插入和搜索的数据结构**。哈希表的本质是一个数组,其思路是:使用哈希函数将 Key 转换为数组下标,利用数组的随机访问特性,使得我们能在 `O(1)` 的时间代价内完成检索。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220320201844.png) + +有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 + +- **哈希集合**是集合数据结构的实现之一,用于存储非重复值。 +- **哈希映射**是映射数据结构的实现之一,用于存储键值对。 + +哈希索引基于哈希表实现,**只适用于等值查询**。对于每一行数据,哈希索引都会将所有的索引列计算一个哈希码(`hashcode`),哈希码是一个较小的值。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。 + +在 Mysql 中,只有 Memory 存储引擎显示支持哈希索引。 + +✔️️️️️ 哈希索引的**优点**: + +- 因为索引数据结构紧凑,所以**查询速度非常快**。 + +❌ 哈希索引的**缺点**: + +- **只支持等值比较查询** - 包括 `=`、`IN()`、`<=>`。 + - **不支持范围查询**,如 `WHERE price > 100`。 + - **不支持模糊查询**,如 `%` 开头。 +- **无法用于排序** - 因为 Hash 索引指向的数据是无序的,因此无法起到排序优化的作用。 +- **不支持联合索引的最左侧原则** - 对于联合索引来说,Hash 索引在计算 Hash 值的时候是将索引键合并后再一起计算 Hash 值,所以不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用。例如:在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 +- **不能用索引中的值来避免读取行** - 因为哈希索引只包含哈希值和行指针,不存储字段,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响不大。 +- 哈希索引有**可能出现哈希冲突** + - 出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 + - 如果哈希冲突多的话,维护索引的代价会很高。 + +> 提示:因为种种限制,所以哈希索引只适用于特定的场合。而一旦使用哈希索引,则它带来的性能提升会非常显著。 + +### B+ 树索引 + +B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用来做 `ORDER BY` 和 `GROUP BY` 操作。因为数据是有序的,所以 B 树也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。 + +通常我们所说的索引是指 B-Tree 索引,它是目前关系型数据库中查找数据最为常用和有效的索引,大多数存储引擎都支持这种索引。使用 B-Tree 这个术语,是因为 MySQL 在`CREATE TABLE`或其它语句中使用了这个关键字,但实际上不同的存储引擎可能使用不同的数据结构,比如 InnoDB 就是使用的 B+Tree。 + +B+Tree 中的 B 是指`balance`,意为平衡。需要注意的是,B+树索引并不能找到一个给定键值的具体行,它找到的只是被查找数据行所在的页,接着数据库会把页读入到内存,再在内存中进行查找,最后得到要查找的数据。 + +#### 二叉搜索树 + +二叉搜索树的特点是:每个节点的左儿子小于父节点,父节点又小于右儿子。其查询时间复杂度是 $$O(log(N))$$。 + +当然为了维持 $$O(log(N))$$ 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 $$O(log(N))$$。 + +随着数据库中数据的增加,索引本身大小随之增加,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘 I/O 消耗,相对于内存存取,I/O 存取的消耗要高几个数量级。可以想象一下一棵几百万节点的二叉树的深度是多少?如果将这么大深度的一颗二叉树放磁盘上,每读取一个节点,需要一次磁盘的 I/O 读取,整个查找的耗时显然是不能够接受的。那么如何减少查找过程中的 I/O 存取次数? + +一种行之有效的解决方法是减少树的深度,将**二叉树变为 N 叉树**(多路搜索树),而 **B+ 树就是一种多路搜索树**。 + +#### B+ 树 + +B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀查找**,其中键前缀查找只适用于最左前缀查找。 + +理解 B+Tree 时,只需要理解其最重要的两个特征即可: + +- 第一,所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 +- 其次,所有的叶子节点由指针连接。如下图为简化了的 B+Tree。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200304235424.jpg) + +#### 聚簇索引和非聚簇索引 + +根据叶子节点的内容,索引类型分为主键索引和非主键索引。 + +- 主键索引又被称为**“聚簇索引(clustered index)”,其叶子节点存的是整行数据**。 + - 聚簇表示数据行和相邻的键值紧凑地存储在一起,因为数据紧凑,所以访问快。 + - 因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。 + - InnoDB 的聚簇索引实际是在同一个结构中保存了 B 树的索引和数据行。 +- 非主键索引又被称为**“二级索引(secondary index)”,其叶子节点存的是主键的值**。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于 249 个。 + +**聚簇索引和非聚簇索引的查询有什么区别** + +- 如果语句是 `select * from T where ID=500`,即聚簇索引查询方式,则只需要搜索主键(ID)索引树; +- 如果语句是 `select * from T where k=5`,即非聚簇索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为**回表**。 + +也就是说,**基于非聚簇索引的查询需要多扫描一棵索引树**。因此,我们在应用中应该尽量使用主键查询。 + +**显然,主键长度越小,非聚簇索引的叶子节点就越小,非聚簇索引占用的空间也就越小。** + +在创建表时,InnoDB 存储引擎会根据不同的场景选择不同的列作为索引: + +- 如果有主键,默认会使用主键作为聚簇索引的索引键(key); +- 如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键(key); +- 在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键(key); + +自增主键是指自增列上定义的主键,在建表语句中一般是这么定义的: `NOT NULL PRIMARY KEY AUTO_INCREMENT`。从性能和存储空间方面考量,自增主键往往是更合理的选择。有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的: + +- 只有一个索引; +- 该索引必须是唯一索引。 + +由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。 + +这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。 + +### 全文索引 + +MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 + +全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。 + +InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 + +全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。 + +全文索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 WHERE 语句的参数匹配。全文索引配合 `match against` 操作使用,而不是一般的 WHERE 语句加 LIKE。它可以在 `CREATE TABLE`,`ALTER TABLE` ,`CREATE INDEX` 使用,不过目前只有 `char`、`varchar`,`text` 列上可以创建全文索引。值得一提的是,在数据量较大时候,先将数据放入一个没有全局索引的表中,然后再用 `CREATE INDEX` 创建全文索引,要比先为一张表建立全文索引然后再将数据写入的速度快很多。 + +```sql +CREATE TABLE `table` ( + `content` text CHARACTER NULL, + ... + FULLTEXT (content) +) +``` + +### 空间数据索引 + +MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 + +必须使用 GIS 相关的函数来维护数据。 + +## 索引的类型 + +主流的关系型数据库一般都支持以下索引类型: + +### 主键索引 + +主键索引:一种特殊的唯一索引,不允许有空值。一个表只能有一个主键(在 InnoDB 中本质上即聚簇索引),一般是在建表的时候同时创建主键索引。 + +```sql +CREATE TABLE `user` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + # ... + PRIMARY KEY (`id`) +); +``` + +### 唯一索引 + +**“唯一索引”确保索引中的值是唯一的,不允许有重复值,如果是组合索引,则列值的组合必须唯一**。 + +在 MySQL 中,可以使用 `CREATE UNIQUE INDEX` 语句来创建唯一索引。 + +直接创建唯一索引: + +```sql +CREATE UNIQUE INDEX `uniq_name` ON `user`(`name`); +``` + +创建表时,添加唯一索引示例: + +```sql +CREATE TABLE `user` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(64) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_name`(`name`) +); +``` + +修改表时,添加唯一索引示例: + +```sql +ALTER TABLE `user` +ADD UNIQUE `uniq_name`(`name`); +``` + +### 普通索引 + +普通索引是最基本的索引,没有任何限制。 + +直接创建索引: + +```sql +CREATE INDEX `idx_name` ON `user`(`name`); +``` + +创建表时,添加索引示例: + +```sql +CREATE TABLE `user` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(64) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + KEY `idx_name`(`name`) +); +``` + +修改表时,添加索引示例: + +```sql +ALTER TABLE `user` +ADD INDEX `idx_name`(`name`); +``` + +### 前缀索引 + +有时候需要索引很长的字符列,这使得存储索引占用大量空间,且导致查询变慢。这种情况下,可以使用前缀索引。 + +**“前缀索引”是指索引开始的部分字符**。对于 `BLOB`/`TEXT` 这种文本类型的列,必须使用前缀索引,因为数据库往往不允许索引这些列的完整长度。 + +✔️️️️ 前缀索引的**优点**是: + +- 可以**大大节约索引空间**,从而**提高索引效率**。 + +❌ 前缀索引的**缺点**是: + +- **会降低索引的区分度**。 +- 此外,**`order by` 无法使用前缀索引,无法把前缀索引用作覆盖索引**。 + +**使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。** + +直接创建前缀索引: + +```sql +CREATE INDEX `idx_name` ON `user`(`name`(10)); +``` + +创建表时,添加前缀索引示例: + +```sql +CREATE TABLE `user` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(64) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + KEY `idx_name`(`name`(10)) +); +``` + +修改表时,添加前缀索引示例: + +```sql +ALTER TABLE `user` +ADD INDEX `idx_name`(`name`(10)); +``` + +> 那么,如何确定前缀索引合适的长度呢? +> + +可以使用下面这个语句,算出这个列上有多少个不同的值: + +```sql +select count(distinct email) as L from SUser; +``` + +然后,依次选取不同长度的前缀来看这个值,比如我们要看一下 4~7 个字节的前缀索引,可以用这个语句: + +```sql +select + count(distinct left(email,4))as L4, + count(distinct left(email,5))as L5, + count(distinct left(email,6))as L6, + count(distinct left(email,7))as L7, +from SUser; +``` + +当然,**使用前缀索引很可能会损失区分度**,所以你需要预先设定一个可以接受的损失比例,比如 5%。然后,在返回的 L4~L7 中,找出不小于 L \* 95% 的值,假设这里 L6、L7 都满足,你就可以选择前缀长度为 6。 + +## 索引优化策略 + +### 索引基本原则 + +- **索引不是越多越好,不要为所有列都创建索引**。要考虑到索引的维护代价、空间占用和查询时回表的代价。索引一定是按需创建的,并且要尽可能确保足够轻量。一旦创建了多字段的联合索引,我们要考虑尽可能利用索引本身完成数据查询,减少回表的成本。 +- 要**尽量避免冗余和重复索引**。 +- 要**考虑删除未使用的索引**。 +- **尽量的扩展索引,不要新建索引**。 + +### 覆盖索引 + +**覆盖索引是指:索引上的信息足够满足查询请求,不需要回表查询数据**。 + +【示例】范围查询 + +```sql +create table T ( +ID int primary key, +k int NOT NULL DEFAULT 0, +s varchar(16) NOT NULL DEFAULT '', +index k(k)) +engine=InnoDB; + +insert into T values(100,1, 'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg'); + +select * from T where k between 3 and 5 +``` + +需要执行几次树的搜索操作,会扫描多少行? + +1. 在 k 索引树上找到 k=3 的记录,取得 ID = 300; +2. 再到 ID 索引树查到 ID=300 对应的 R3; +3. 在 k 索引树取下一个值 k=5,取得 ID=500; +4. 再回到 ID 索引树查到 ID=500 对应的 R4; +5. 在 k 索引树取下一个值 k=6,不满足条件,循环结束。 + +在这个过程中,**回到聚簇索引树搜索的过程,称为“回表”**。可以看到,这个查询过程读了 k 索引树的 3 条记录(步骤 1、3 和 5),回表了两次(步骤 2 和 4)。 + +如果执行的语句是 `select ID from T where k between 3 and 5`,这时只需要查 ID 的值,而 ID 的值已经在 k 索引树上了,因此可以直接提供查询结果,不需要回表。索引包含所有需要查询的字段的值,称为覆盖索引。 + +**由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段**。 + +### 最左匹配原则 + +不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。**这里的最左,可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符**。 + +如果是联合索引,那么 key 也由多个列组成,同时,索引只能用于查找 key 是否**存在(相等)**,遇到范围查询 (`>`、`<`、`BETWEEN`、`LIKE`) 就**不能进一步匹配**了,后续退化为线性查找。因此,**列的排列顺序决定了可命中索引的列数**。 + +**应该将选择性高的列或基数大的列优先排在多列索引最前列**。但有时,也需要考虑 `WHERE` 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。 + +**“索引的选择性”是指不重复的索引值和记录总数的比值**,最大值为 1,此时每个记录都有唯一的索引与其对应。索引的选择性越高,查询效率越高。如果存在多条命中前缀索引的情况,就需要依次扫描,直到最终找到正确记录。 + +例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 + +```sql +SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, +COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, +COUNT(*) +FROM payment; +``` + +```batch + staff_id_selectivity: 0.0001 +customer_id_selectivity: 0.0373 + COUNT(*): 16049 +``` + +### 使用索引来排序 + +Mysql 有两种方式可以生成排序结果:通过排序操作;或者按索引顺序扫描。 + +**索引最好既满足排序,又用于查找行**。这样,就可以通过命中覆盖索引直接将结果查出来,也就不再需要排序了。 + +这样整个查询语句的执行流程就变成了: + +1. 从索引 (city,name,age) 找到第一个满足 city='杭州’条件的记录,取出其中的 city、name 和 age 这三个字段的值,作为结果集的一部分直接返回; +2. 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回; +3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。 + +### = 和 in 可以乱序 + +**不需要考虑 `=`、`IN` 等的顺序**,Mysql 会自动优化这些条件的顺序,以匹配尽可能多的索引列。 + +【示例】如有索引 (a, b, c, d),查询条件 `c > 3 and b = 2 and a = 1 and d < 4` 与 `a = 1 and c > 3 and b = 2 and d < 4` 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c、d。 + +## 索引失效的场景 + +创建了索引,并非一定有效。比如不满足前缀索引、最左前缀匹配原则、查询条件涉及函数计算等情况都无法使用索引。此外,即使 SQL 本身符合索引的使用条件,MySQL 也会通过评估各种查询方式的代价,来决定是否走索引,以及走哪个索引。 + +### 对索引使用左模糊匹配 + +使用左或者左右模糊匹配的时候,也就是 `like %xx` 或者 `like %xx%` 这两种方式都会造成索引失效。这是因为:**B+ 树索引是按照“索引值”有序存储的,只能根据前缀进行比较。** + +### 对索引使用函数或表达式 + +**查询语句中,如果对索引字段使用“函数”或“表达式”,会导致索引失效**。 + +因为索引树存储的是索引字段的原始值,因此无法索引经过函数计算或表达式计算后的值。 + +❌ 错误示例: + +```sql +SELECT actor_id FROM actor WHERE actor_id + 1 = 5; +SELECT ... WHERE TO_DAYS(current_date) - TO_DAYS(date_col) <= 10; +``` + +### 对索引隐式类型转换 + +查询语句中,如果对索引字段进行隐式类型转换,会导致索引失效。由于隐式类型转换是通过 `CAST` 函数实现的,等同于对索引列使用了函数,所以会导致索引失效。 + +### 联合索引不遵循最左匹配原则 + +联合索引如果不遵循最左匹配原则,就会导致索引失效。原因是,在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序。 + +### 索引列判空 + +**索引列与 `NULL` 或者 `NOT NULL` 进行判断的时候也会失效**。这是因为索引并不存储空值,所以最好在设计数据表的时候就将字段设置为 `NOT NULL` 约束,比如你可以将 INT 类型的字段,默认值设置为 0。将字符类型的默认值设置为空字符串 (’’)。 + +### WHERE 子句中的 OR 前后条件存在非索引列 + +在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。 + +比如下面的 SQL 语句,comment_id 是主键,而 comment_text 没有进行索引,因为 OR 的含义就是两个只要满足一个即可,因此只有一个条件列进行了索引是没有意义的,只要有条件列没有进行索引,就会进行全表扫描,因此索引的条件列也会失效: + +``` +EXPLAIN SELECT comment_id, user_id, comment_text FROM product_comment WHERE comment_id = 900001 OR comment_text = '462eed7ac6e791292a79' +``` + +运行结果: + +``` ++----+-------------+-----------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-----------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ +| 1 | SIMPLE | product_comment | NULL | ALL | PRIMARY | NULL | NULL | NULL | 996663 | 10.00 | Using where | ++----+-------------+-----------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ +``` + +如果我们把 comment_text 创建了索引会是怎样的呢? + +``` ++----+-------------+-----------------+------------+-------------+----------------------+----------------------+---------+------+------+----------+------------------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-----------------+------------+-------------+----------------------+----------------------+---------+------+------+----------+------------------------------------------------+ +| 1 | SIMPLE | product_comment | NULL | index_merge | PRIMARY,comment_text | PRIMARY,comment_text | 4,767 | NULL | 2 | 100.00 | Using union(PRIMARY,comment_text); Using where | ++----+-------------+-----------------+------------+-------------+----------------------+----------------------+---------+------+------+----------+------------------------------------------------+ +``` + +你能看到这里使用到了 index merge,简单来说 index merge 就是对 comment_id 和 comment_text 分别进行了扫描,然后将这两个结果集进行了合并。这样做的好处就是避免了全表扫描。 + +## 参考资料 + +- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) +- [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\344\272\213\345\212\241.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\344\272\213\345\212\241.md" new file mode 100644 index 0000000000..09a55c642b --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\344\272\213\345\212\241.md" @@ -0,0 +1,605 @@ +--- +icon: logos:mysql +title: Mysql 事务 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202310260703504.png +date: 2020-06-03 19:32:09 +order: 04 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 事务 +permalink: /pages/00b04d/ +--- + +# Mysql 事务 + +> 不是所有的 Mysql 存储引擎都实现了事务处理。支持事务的存储引擎有:`InnoDB` 和 `NDB Cluster`。不支持事务的存储引擎,代表有:`MyISAM`。 +> +> 用户可以根据业务是否需要事务处理(事务处理可以保证数据安全,但会增加系统开销),选择合适的存储引擎。 + +## 事务简介 + +### 事务概念 + +**“事务”指的是满足 ACID 特性的一组操作**。事务内的 SQL 语句,要么全执行成功,要么全执行失败。可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030701337.png) + +### ACID + +ACID 是数据库事务正确执行的四个基本要素。 + +- **原子性(Atomicity)** + - 事务被视为不可分割的最小单元,事务中的所有操作要么全部提交成功,要么全部失败回滚。 + - 回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 +- **一致性(Consistency)** + - 数据库在事务执行前后都保持一致性状态。 + - 在一致性状态下,所有事务对一个数据的读取结果都是相同的。 +- **隔离性(Isolation)** + - 一个事务所做的修改在最终提交以前,对其它事务是不可见的。 +- **持久性(Durability)** + - 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 + - 可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 + +一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性。 + +- 只有满足一致性,事务的执行结果才是正确的。 +- 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。 +- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 +- 事务满足持久化是为了能应对系统崩溃的情况。 + +### 事务操作 + +事务相关的语句如下: + +- `BEGIN` / `START TRANSACTION` - **用于标记事务的起始点**。 +- `START TRANSACTION WITH CONSISTENT SNAPSHOT` - **用于标记事务的起始点**。 +- `SAVEPOINT` - **用于创建保存点**。方便后续针对保存点进行回滚。一个事务中可以存在多个保存点。 +- `RELEASE SAVEPOINT` - 删除某个保存点。 +- `ROLLBACK TO` - **用于回滚到指定的保存点**。如果没有设置保存点,则回退到 `START TRANSACTION` 语句处。 +- `COMMIT` - **提交事务**。 +- `SET TRANSACTION` - 设置事务的隔离级别。 + +> 注意: +> +> 两种开启事务的命令,启动时机是不同的: +> +> - 执行了 `BEGIN` / `START TRANSACTION` 命令后,并不代表事务立刻启动,而是当执行了增删查操作时,才真正启动事务。 +> - 执行了 `START TRANSACTION WITH CONSISTENT SNAPSHOT` 命令,会立刻启动事务。 + +事务处理示例: + +(1)创建一张示例表 + +```sql +-- 撤销表 user +DROP TABLE IF EXISTS `user`; + +-- 创建表 user +CREATE TABLE `user` ( + `id` INT(10) UNSIGNED NOT NULL COMMENT 'Id', + `username` VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '用户名', + `password` VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码', + `email` VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱' +) COMMENT ='用户表'; +``` + +(2)执行事务操作 + +```sql +-- 开始事务 +START TRANSACTION; + +-- 插入操作 A +INSERT INTO `user` +VALUES (1, 'root1', 'root1', 'xxxx@163.com'); + +-- 创建保留点 updateA +SAVEPOINT `updateA`; + +-- 插入操作 B +INSERT INTO `user` +VALUES (2, 'root2', 'root2', 'xxxx@163.com'); + +-- 回滚到保留点 updateA +ROLLBACK TO `updateA`; + +-- 提交事务,只有操作 A 生效 +COMMIT; +``` + +(3)查询结果 + +```sql +SELECT * FROM `user`; +``` + +结果: + +```sql +mysql> SELECT * FROM user; ++----+----------+----------+--------------+ +| id | username | password | email | ++----+----------+----------+--------------+ +| 1 | root1 | root1 | xxxx@163.com | ++----+----------+----------+--------------+ +1 row in set (0.02 sec) +``` + +### AUTOCOMMIT + +**MySQL 默认采用隐式提交策略(`autocommit`)**。每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。 + +通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。 + +```sql +-- 查看 AUTOCOMMIT +SHOW VARIABLES LIKE 'AUTOCOMMIT'; + +-- 关闭 AUTOCOMMIT +SET autocommit = 0; + +-- 开启 AUTOCOMMIT +SET autocommit = 1; +``` + +## 并发一致性问题 + +在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。 + +### 丢失修改 + +**“丢失修改”是指一个事务的更新操作被另外一个事务的更新操作替换**。 + +如下图所示,T1 和 T2 两个事务对同一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706586.png) + +### 脏读 + +**“脏读(dirty read)”是指当前事务可以读取其他事务未提交的数据**。 + +如下图所示,T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706587.png) + +### 不可重复读 + +**“不可重复读(non-repeatable read)”是指一个事务内多次读取同一数据,过程中,该数据被其他事务所修改,导致当前事务多次读取的数据可能不一致**。 + +如下图所示,T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706588.png) + +### 幻读 + +**“幻读(phantom read)”是指一个事务内多次读取同一范围的数据,过程中,其他事务在该数据范围新增了数据,导致当前事务未发现新增数据**。 + +事务 T1 读取某个范围内的记录时,事务 T2 在该范围内插入了新的记录,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706589.png) + +## 事务隔离级别 + +### 事务隔离级别简介 + +为了解决以上提到的[“并发一致性问题”](#并发一致性问题),SQL 标准提出了四种“事务隔离级别”来应对这些问题。事务隔离级别等级越高,越能保证数据的一致性和完整性,但是执行效率也越低。因此,设置数据库的事务隔离级别时需要做一下权衡。 + +事务隔离级别从低到高分别是: + +- **“读未提交(read uncommitted)”** - 是指,**事务中的修改,即使没有提交,对其它事务也是可见的**。 +- **“读已提交(read committed)” ** - 是指,**事务提交后,其他事务才能看到它的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 + - **读已提交解决了脏读的问题**。 + - 读已提交是大多数数据库的默认事务隔离级别,如 Oracle。 +- **“可重复读(repeatable read)”** - 是指:**保证在同一个事务中多次读取同样数据的结果是一样的**。 + - **可重复读解决了不可重复读问题**。 + - **可重复读是 InnoDB 存储引擎的默认事务隔离级别**。 +- **串行化(serializable )** - 是指,**强制事务串行执行**,对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁。 + - **串行化解决了幻读问题**。由于强制事务串行执行,自然避免了所有的并发问题。 + - **串行化策略会在读取的每一行数据上都加锁**,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。 + +事务隔离级别对并发一致性问题的解决情况: + +| 隔离级别 | 丢失修改 | 脏读 | 不可重复读 | 幻读 | +| :------: | :------: | :--: | :--------: | :--: | +| 读未提交 | ✔️️️ | ❌ | ❌ | ❌ | +| 读已提交 | ✔️️️ | ✔️️️ | ❌ | ❌ | +| 可重复读 | ✔️️️ | ✔️️️ | ✔️️️ | ❌ | +| 可串行化 | ✔️️️ | ✔️️️ | ✔️️️ | ✔️️️ | + +### 查看和设置事务隔离级别 + +可以通过 `SHOW VARIABLES LIKE 'transaction_isolation'` 语句查看事务隔离级别。 + +【示例】查看事务隔离示例 + +```sql +mysql> SHOW VARIABLES LIKE 'transaction_isolation'; ++-----------------------+-----------------+ +| Variable_name | Value | ++-----------------------+-----------------+ +| transaction_isolation | REPEATABLE-READ | ++-----------------------+-----------------+ +1 row in set (0.03 sec) +``` + +MySQL 提供了 `SET TRANSACTION` 语句,该语句可以改变单个会话或全局的事务隔离级别。语法格式如下: + +```sql +SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} +``` + +其中,`SESSION` 和 `GLOBAL` 关键字用来指定修改的事务隔离级别的范围: + +- `SESSION` - 表示修改的事务隔离级别,将应用于当前会话内的所有事务。 +- `GLOBAL` - 表示修改的事务隔离级别,将应用于所有会话内的所有事务(即全局修改),且当前已经存在的会话不受影响; +- 如果省略 `SESSION` 和 `GLOBAL`,表示修改的事务隔离级别,将应用于当前会话内的下一个还未开始的事务。 + +【示例】设置事务隔离示例 + +```sql +-- 设置事务隔离级别为 READ UNCOMMITTED +SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + +-- 设置事务隔离级别为 READ COMMITTED +SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; + +-- 设置事务隔离级别为 REPEATABLE READ +SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; + +-- 设置事务隔离级别为 SERIALIZABLE +SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; +``` + +### 事务隔离级别实现方式 + +Mysql 中的事务功能是在存储引擎层实现的,**并非所有存储引擎都支持事务功能**。InnoDB 是 Mysql 的首先事务存储引擎。 + +四种隔离级别具体是如何实现的呢? + +以 InnoDB 的事务实现来说明: + +- 对于“读未提交”隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了; +- 对于“串行化”隔离级别的事务来说,通过加读写锁的方式来避免并行访问; +- 对于“读提交”和“可重复读”隔离级别的事务来说,它们都是通过 ReadView 来实现的,区别仅在于创建 ReadView 的时机不同。ReadView 可以理解为一个数据快照。 + - “读提交”隔离级别是在“每个语句执行前”都会重新生成一个 ReadView + - “可重复读”隔离级别是在“启动事务时”生成一个 ReadView,然后整个事务期间都在用这个 ReadView。 + +> 关于 ReadView 更多细节,将在 MVCC 章节中阐述。 + +## MVCC + +### 当前读和快照读 + +在高并发场景下,多事务同时执行,可能会出现种种并发一致性问题。最常见,也是最容易想到的解决问题思路就是:对访问的数据加锁,通过强制互斥来解决问题。但是,加锁就意味着阻塞,势必会增加响应时间,降低系统整体吞吐量。在大多数真实的业务场景中,读请求远大于写请求,由于读请求并不会修改数据,自然也不存在一致性问题,因此为占大多数的读请求加锁是一种不必要的开销。那么,我们很自然的会想到,如果只针对写操作加锁,不就能大大提升吞吐量了吗?没错,有一种名为**“写时复制(Copy-On-Write,简称 COW)”**的技术,正是基于这个想法而设计,并广泛应用于各种软件领域,例如:Java 中的 `CopyOnWriteArrayList` 等容器;Redis 中的 RDB 持久化过程。 + +Copy-On-Write 的核心思想是:假设有多个请求需要访问相同的数据,先为这份数据生成一个副本(也可以称为快照)。然后将读写分离,所有的读请求都直接访问原数据;所有的写请求都访问副本数据,为了实现并发一致性,写数据时需要通过加锁保证每次写操作只能由一个写请求完成。当写操作完成后,用副本数据替换原数据。 + +在 Mysql 中,也采用了 Copy-On-Write 设计思想,将读写分离。 + +- 这里的“写”指的是当前读。**“当前读”,顾名思义,指的是读取记录当前的数据。**为了保证读取当前数据时,没有其他事务修改,因此需要对读取记录加锁。当前读的场景有下面几种: + - `INSERT` - 插入操作 + - `UPDATE` - 更新操作 + - `DELETE` - 删除操作 + - `SELECT ... LOCK IN SHARE MODE` - 加共享锁(读锁) + - `SELECT ... FOR UPDATE` - 加独享锁(写锁) +- 这里的“读”指的是快照读。**“快照读”,顾名思义,指的是读取记录的某个历史快照版本**。不加锁的普通 `SELECT` 都属于快照读,例如:`SELECT ... FROM`。采用快照读的前提是,事务隔离级别不是串行化级别。串行化级别下的快照读会退化成当前读。快照读的实现是基于 MVCC。 + +### 什么是 MVCC + +> 前文提到,快照读的实现是基于 MVCC。那么,什么是 MVCC 呢? + +**MVCC 是 Multi Version Concurrency Control 的缩写,即“多版本并发控制”**。MVCC 的设计目标是提高数据库的并发性,采用非阻塞的方式去处理读/写并发冲突,可以将其看成一种乐观锁。 + +不仅是 Mysql,包括 Oracle、PostgreSQL 等其他关系型数据库都实现了各自的 MVCC,实现机制没有统一标准。**MVCC 是 InnoDB 存储引擎实现事务隔离级别的一种具体方式**。其主要用于实现读已提交和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 + +### MVCC 实现原理 + +MVCC 的实现原理,主要基于隐式字段、UndoLog、ReadView 来实现。 + +#### 隐式字段 + +InnoDB 存储引擎中,数据表的每行记录,除了用户显示定义的字段以外,还有几个数据库隐式定义的字段: + +- `DB_ROW_ID` - **隐藏的自增 ID**,如果数据表没有指定主键,InnoDB 会自动基于 `row_id` 产生一个聚簇索引。 +- `DB_TRX_ID` - **最近修改的事务 ID**。事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里; +- `DB_ROLL_PTR` - **回滚指针**,指向这条记录的上一个版本。 + +#### UndoLog + +MVCC 的多版本指的是多个版本的快照,快照存储在 UndoLog 中。该日志通过回滚指针 `roll_pointer` 把一个数据行的所有快照链接起来,构成一个**版本链**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030708591.png) + +#### ReadView + +**ReadView 就是事务进行快照读时产生的读视图(快照)**。 + +ReadView 有四个重要的字段: + +- `m_ids` - 指的是在创建 ReadView 时,当前数据库中“活跃事务”的事务 ID 列表。注意:这是一个列表,**“活跃事务”指的就是,启动了但还没提交的事务**。 +- `min_trx_id` - 指的是在创建 ReadView 时,当前数据库中“活跃事务”中事务 id 最小的事务,也就是 `m_ids` 的最小值。 +- `max_trx_id` - 这个并不是 m_ids 的最大值,而是指创建 ReadView 时当前数据库中应该给下一个事务分配的 ID 值,也就是全局事务中最大的事务 ID 值 + 1; +- `creator_trx_id` - 指的是创建该 ReadView 的事务的事务 ID。 + +在创建 ReadView 后,我们可以将记录中的 trx_id 划分为三种情况: + +- 已提交事务 +- 已启动但未提交的事务 +- 未启动的事务 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030708265.png) + +> ReadView 如何判断版本链中哪个版本可见? + +一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况: + +- `trx_id == creator_trx_id` - 表示 `trx_id` 版本记录由 ReadView 所代表的当前事务产生,当然可以访问。 +- `trx_id < min_trx_id` - 表示 `trx_id` 版本记录是在创建 ReadView 之前已提交的事务生成的,当前事务可以访问。 +- `trx_id >= max_trx_id` - 表示 `trx_id` 版本记录是在创建 ReadView 之后才启动的事务生成的,当前事务不可以访问。 +- `min_trx_id <= trx_id < max_trx_id` - 需要判断 `trx_id` 是否在 `m_ids` 列表中 + - 如果 `trx_id` 在 `m_ids` 列表中,表示生成 `trx_id` 版本记录的事务依然活跃(未提交事务),当前事务不可以访问。 + - 如果 `trx_id` 不在 `m_ids` 列表中,表示生成 `trx_id` 版本记录的事务已提交,当前事务可以访问。 + +这种通过“版本链”来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。 + +### MVCC 如何实现多种事务隔离级别 + +对于“读已提交”和“可重复读”隔离级别的事务来说,它们都是通过 MVCC 的 ReadView 机制来实现的,区别仅在于创建 ReadView 的时机不同。ReadView 可以理解为一个数据快照。 + +- “读已提交”隔离级别,会在“每个语句执行前”都会重新生成一个 ReadView。 +- “可重复读”隔离级别,会在“启动事务时”生成一个 ReadView,然后整个事务期间都在复用这个 ReadView。 + +MySQL InnoDB 引擎的默认隔离级别虽然是“可重复读”,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种: + +- 针对**快照读**(普通 select 语句),**通过 MVCC 方式解决了幻读**,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。 +- 针对**当前读**(select ... for update 等语句),**通过 Next-Key Lock(记录锁+间隙锁)方式解决了幻读**,因为当执行 select ... for update 语句的时候,会加上 Next-Key Lock,如果有其他事务在 Next-Key Lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。 + +#### MVCC 实现可重复读 + +**可重复读隔离级别只有在启动事务时才会创建 ReadView,然后整个事务期间都使用这个 ReadView**。这样就保证了在事务期间读到的数据都是事务启动前的记录。 + +举个例子,假设有两个事务依次执行以下操作: + +- 初始,表中 id = 1 的 value 列值为 100。 +- 事务 2 读取数据,value 为 100; +- 事务 1 将 value 设为 200; +- 事务 2 读取数据,value 为 100; +- 事务 1 提交事务; +- 事务 2 读取数据,value 依旧为 100; + +以上操作,如下图所示。T2 事务在事务过程中,是否可以看到 T1 事务的修改,可以根据 [ReadView](#ReadView) 中描述的规则去判断。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030709235.png) + +从图中不难看出: + +- 对于 `trx_id = 100` 的版本记录,比对 T2 事务 ReadView ,`trx_id < min_trx_id`,因此在 T2 事务中的任意时刻都可见; +- 对于 `trx_id = 101` 的版本记录,比对 T2 事务 ReadView ,可以看出 `min_trx_id <= trx_id < max_trx_id` ,且 `trx_id` 在 `m_ids` 中,因此 T2 事务中不可见。 + +综上所述,在 T2 事务中,自始至终只能看到 `trx_id = 100` 的版本记录。 + +#### MVCC 实现读已提交 + +**读已提交隔离级别每次读取数据时都会创建一个 ReadView**。这意味着,事务期间的多次读取同一条数据,前后读取的数据可能会出现不一致——因为,这期间可能有另外一个事务修改了该记录,并提交了事务。 + +举个例子,假设有两个事务依次执行以下操作: + +- 初始,表中 id = 1 的 value 列值为 100。 +- 事务 2 读取数据(创建 ReadView),value 为 0; +- 事务 1 将 value 设为 100; +- 事务 2 读取数据(创建 ReadView),value 为 0; +- 事务 1 提交事务; +- 事务 2 读取数据(创建 ReadView),value 为 100; + +以上操作,如下图所示,T2 事务在事务过程中,是否可以看到其他事务的修改,可以根据 [ReadView](#ReadView) 中描述的规则去判断。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030709986.png) + +从图中不难看出: + +- 对于 `trx_id = 100` 的版本记录,比对 T2 事务 ReadView ,`trx_id < min_trx_id`,因此在 T2 事务中的任意时刻都可见; +- 对于 `trx_id = 101` 的版本记录,比对 T2 事务 ReadView ,可以看出第二次查询时(T1 更新未提交),`min_trx_id <= trx_id < max_trx_id` ,且 `trx_id` 在 `m_ids` 中,因此 T2 事务中不可见;而第三次查询时(T1 更新已提交),`trx_id < min_trx_id`,因此在 T2 事务中可见; + +综上所述,在 T2 事务中,当 T1 事务提交前,可读取到的是 `trx_id = 100` 的版本记录;当 T1 事务提交后,可读取到的是 `trx_id = 101` 的版本记录。 + +#### MVCC + Next-Key Lock 解决幻读 + +MySQL InnoDB 引擎的默认隔离级别虽然是“可重复读”,但是它很大程度上避免幻读现象(并不是完全解决了)。针对快照读和当前读,InnoDB 的处理方式各不相同。 + +> 快照读是如何避免幻读的? + +针对**快照读**(普通 `SELECT` 语句),**通过 MVCC 方式解决了幻读**,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。 + +> 当前读是如何避免幻读的? + +针对**当前读**(`SELECT ... FOR UPDATE` 等语句),**通过 Next-Key Lock(记录锁+间隙锁)方式解决了幻读**,因为当执行 `SELECT ... FOR UPDATE` 语句的时候,会加上 Next-Key Lock,如果有其他事务在 Next-Key Lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好的避免了幻读问题。 + +> 幻读被完全解决了吗? + +**可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读**。 + +【示例】幻读案例一 + +环境:存储引擎为 InnoDB;事务隔离级别为可重复读 + +```sql +-- -------------------------------------------------------------------------------------- +-- 实验说明:以下 SQL 脚本必须严格按照顺序执行,并且事务 A 和事务 B 必须在不同的 Client 中执行。 +-- ---------------------------------------------------------------------------------------- + +-- --------------------------------------------------------------------- (1)数据初始化 + +-- 创建表 test +CREATE TABLE `test` ( + `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `value` INT(10) NOT NULL +); + +-- 数据初始化 +INSERT INTO `test` (`id`, `value`) VALUES (1, 1); +INSERT INTO `test` (`id`, `value`) VALUES (2, 2); +INSERT INTO `test` (`id`, `value`) VALUES (3, 3); + +-- --------------------------------------------------------------------- (2)事务 A + +BEGIN; + +-- 查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果为空 + +-- --------------------------------------------------------------------- (3)事务 B + +BEGIN; + +INSERT INTO `test` (`id`, `value`) VALUES (4, 4); + +COMMIT; + +-- --------------------------------------------------------------------- (4)事务 A + +-- 查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果依然为空 + +-- 成功更新本应看不到的记录 id = 4 +UPDATE `test` SET `value` = 0 WHERE `id` = 4; + +-- 再一次查询 id = 4 的记录 +SELECT * FROM `test` WHERE `id` = 4; +-- 结果为: +-- +----+-------+ +-- | id | value | +-- +----+-------+ +-- | 4 | 0 | +-- +----+-------+ + +COMMIT; +``` + +以上示例代码的时序图如下: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311070630072.png) + +【示例】幻读案例二 + +```sql +-- --------------------------------------------------------------------- (1)数据初始化 + +-- 创建表 test +CREATE TABLE `test` ( + `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `value` INT(10) NOT NULL +); + +-- 数据初始化 +INSERT INTO `test` (`id`, `value`) VALUES (1, 1); +INSERT INTO `test` (`id`, `value`) VALUES (2, 2); +INSERT INTO `test` (`id`, `value`) VALUES (3, 3); + +-- --------------------------------------------------------------------- (2)事务 A + +BEGIN; + +-- 查询 id > 2 的记录数 +SELECT COUNT(*) FROM `test` WHERE `id` > 2; +-- 结果为: +-- +----------+ +-- | count(*) | +-- +----------+ +-- | 1 | +-- +----------+ + +-- --------------------------------------------------------------------- (3)事务 B + +BEGIN; + +INSERT INTO `test` (`id`, `value`) VALUES (4, 4); + +COMMIT; + +-- --------------------------------------------------------------------- (4)事务 A + +-- 查询 id > 2 的记录数 +SELECT COUNT(*) FROM `test` WHERE `id` > 2 FOR UPDATE; +-- 结果为: +-- +----------+ +-- | count(*) | +-- +----------+ +-- | 2 | +-- +----------+ + +COMMIT; +``` + +**要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 `select ... for update` 这类当前读的语句**,因为它会对记录加 Next-Key Lock,从而避免其他事务插入一条新记录。 + +## 分布式事务 + +在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为 **本地事务**。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 + +**分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性。** + +分布式事务的常见方案如下: + +- **两阶段提交(2PC)** - 将事务的提交过程分为两个阶段来进行处理:准备阶段和提交阶段。参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。 +- **三阶段提交(3PC)** - 与二阶段提交不同的是,引入超时机制。同时在协调者和参与者中都引入超时机制。将二阶段的准备阶段拆分为 2 个阶段,插入了一个 preCommit 阶段,使得原先在二阶段提交中,参与者在准备之后,由于协调者发生崩溃或错误,而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题得以解决。 +- **补偿事务(TCC)** + - **Try** - 操作作为一阶段,负责资源的检查和预留。 + - **Confirm** - 操作作为二阶段提交操作,执行真正的业务。 + - **Cancel** - 是预留资源的取消。 +- **本地消息表** - 在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。 +- **MQ 事务** - 基于 MQ 的分布式事务方案其实是对本地消息表的封装。 +- **SAGA** - Saga 事务核心思想是将长事务拆分为多个本地短事务,由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。 + +分布式事务方案分析: + +- 2PC/3PC 依赖于数据库,能够很好的提供强一致性和强事务性,但相对来说延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。 +- TCC 适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。 +- 本地消息表/MQ 事务 都适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。 +- Saga 事务 由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。 Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。 + +> 分布式事务详细说明、分析请参考:[分布式事务基本原理](https://dunwu.github.io/waterdrop/pages/e1881c/) + +## 事务最佳实践 + +高并发场景下的事务到底该如何调优? + +### 尽量使用低级别事务隔离 + +结合业务场景,尽量使用低级别事务隔离 + +### 避免行锁升级表锁 + +在 InnoDB 中,行锁是通过索引实现的,如果不通过索引条件检索数据,行锁将会升级到表锁。我们知道,表锁是会严重影响到整张表的操作性能的,所以应该尽力避免。 + +### 缩小事务范围 + +有时候,数据库并发访问量太大,会出现以下异常: + +``` +MySQLQueryInterruptedException: Query execution was interrupted +``` + +高并发时对一条记录进行更新的情况下,由于更新记录所在的事务还可能存在其他操作,导致一个事务比较长,当有大量请求进入时,就可能导致一些请求同时进入到事务中。 + +又因为锁的竞争是不公平的,当多个事务同时对一条记录进行更新时,极端情况下,一个更新操作进去排队系统后,可能会一直拿不到锁,最后因超时被系统打断踢出。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630112600.png) + +如上图中的操作,虽然都是在一个事务中,但锁的申请在不同时间,只有当其他操作都执行完,才会释放所有锁。因为扣除库存是更新操作,属于行锁,这将会影响到其他操作该数据的事务,所以我们应该尽量避免长时间地持有该锁,尽快释放该锁。又因为先新建订单和先扣除库存都不会影响业务,所以我们可以将扣除库存操作放到最后,也就是使用执行顺序 1,以此尽量减小锁的持有时间。 + +**在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。** + +知道了这个设定,对我们使用事务有什么帮助呢?那就是,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 + +## 参考资料 + +- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" deleted file mode 100644 index 1b7e68a2a4..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/04.Mysql\351\224\201.md" +++ /dev/null @@ -1,209 +0,0 @@ ---- -title: Mysql 锁 -date: 2020-09-07 07:54:19 -categories: - - 数据库 - - 关系型数据库 - - Mysql -tags: - - 数据库 - - 关系型数据库 - - Mysql - - 锁 -permalink: /pages/f1f151/ ---- - -# Mysql 锁 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716064947.png) - -## 悲观锁和乐观锁 - -确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。** - -- **`悲观锁`** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作 - - 在查询完数据的时候就把事务锁起来,直到提交事务(`COMMIT`) - - 实现方式:**使用数据库中的锁机制**。 -- **`乐观锁`** - 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 - - 在修改数据的时候把事务锁起来,通过 version 的方式来进行锁定 - - 实现方式:**使用 version 版本或者时间戳**。 - -【示例】乐观锁示例 - -商品 goods 表中有一个字段 status,status 为 1 代表商品未被下单,status 为 2 代表商品已经被下单,那么我们对某个商品下单时必须确保该商品 status 为 1。假设商品的 id 为 1。 - -```sql -select (status,status,version) from t_goods where id=#{id} - -update t_goods -set status=2,version=version+1 -where id=#{id} and version=#{version}; -``` - -> 更详细的乐观锁说可以参考:[使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) - -## 表级锁和行级锁 - -从数据库的锁粒度来看,MySQL 中提供了两种封锁粒度:行级锁和表级锁。 - -- **表级锁(table lock)** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。 -- **行级锁(row lock)** - 锁定指定的行记录。这样其它进程还是可以对同一个表中的其它记录进行操作。 - -应该尽量只锁定需要修改的那部分数据,而不是所有的资源。**锁定的数据量越少,锁竞争的发生频率就越小,系统的并发程度就越高**。但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销。因此**锁粒度越小,系统开销就越大**。 - -在选择锁粒度时,需要在锁开销和并发程度之间做一个权衡。 - -在 `InnoDB` 中,**行锁是通过给索引上的索引项加锁来实现的**。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。 - -## 读写锁 - -- 独享锁(Exclusive),简写为 X 锁,又称写锁。使用方式:`SELECT ... FOR UPDATE;` -- 共享锁(Shared),简写为 S 锁,又称读锁。使用方式:`SELECT ... LOCK IN SHARE MODE;` - -写锁和读锁的关系,简言之:**独享锁存在,其他事务就不能做任何操作**。 - -**`InnoDB` 下的行锁、间隙锁、next-key 锁统统属于独享锁**。 - -## 意向锁 - -**当存在表级锁和行级锁的情况下,必须先申请意向锁(表级锁,但不是真的加锁),再获取行级锁**。使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 - -**意向锁是 `InnoDB` 自动加的,不需要用户干预**。 - -在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。 - -意向锁规定: - -- IX/IS 是表锁; -- X/S 是行锁。 -- 一个事务在获得某个数据行的 S 锁之前,必须先获得表的 IS 锁或者更强的锁; -- 一个事务在获得某个数据行的 X 锁之前,必须先获得表的 IX 锁。 - -通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。 - -各种锁的兼容关系如下: - -| - | X | IX | S | IS | -| :-: | :-: | :-: | :-: | :-: | -| X | ❌ | ❌ | ❌ | ❌ | -| IX | ❌ | ✔️ | ❌ | ✔️ | -| S | ❌ | ❌ | ✔️ | ✔️ | -| IS | ❌ | ✔️ | ✔️ | ✔️ | - -解释如下: - -- 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁; -- 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T1 想要对数据行 R1 加 X 锁,事务 T2 想要对同一个表的数据行 R2 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。) - -## MVCC - -**多版本并发控制(Multi-Version Concurrency Control, MVCC)可以视为行级锁的一个变种。它在很多情况下都避免了加锁操作,因此开销更低**。不仅是 Mysql,包括 Oracle、PostgreSQL 等其他数据库都实现了各自的 MVCC,实现机制没有统一标准。 - -MVCC 是 `InnoDB` 存储引擎实现隔离级别的一种具体方式,**用于实现提交读和可重复读这两种隔离级别**。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 - -### MVCC 思想 - -加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的。 - -MVCC 的思想是: - -- **保存数据在某个时间点的快照,写操作(DELETE、INSERT、UPDATE)更新最新的版本快照;而读操作去读旧版本快照,没有互斥关系**。这一点和 `CopyOnWrite` 类似。 -- 脏读和不可重复读最根本的原因是**事务读取到其它事务未提交的修改**。在事务进行读取操作时,为了解决脏读和不可重复读问题,**MVCC 规定只能读取已经提交的快照**。当然一个事务可以读取自身未提交的快照,这不算是脏读。 - -### 版本号 - -InnoDB 的 MVCC 实现是:在每行记录后面保存两个隐藏列,一个列保存行的创建时间,另一个列保存行的过期时间(这里的时间是指系统版本号)。每开始一个新事务,系统版本号会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 - -- 系统版本号 `SYS_ID`:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。 -- 事务版本号 `TRX_ID` :事务开始时的系统版本号。 - -### Undo 日志 - -MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 `ROLL_PTR` 把一个数据行的所有快照连接起来。 - -例如在 MySQL 创建一个表 t,包含主键 id 和一个字段 x。我们先插入一个数据行,然后对该数据行执行两次更新操作。 - -```sql -INSERT INTO t(id, x) VALUES(1, "a"); -UPDATE t SET x="b" WHERE id=1; -UPDATE t SET x="c" WHERE id=1; -``` - -因为没有使用 `START TRANSACTION` 将上面的操作当成一个事务来执行,根据 MySQL 的 `AUTOCOMMIT` 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。 - -`INSERT`、`UPDATE`、`DELETE` 操作会创建一个日志,并将事务版本号 `TRX_ID` 写入。`DELETE` 可以看成是一个特殊的 `UPDATE`,还会额外将 DEL 字段设置为 1。 - -### ReadView - -MVCC 维护了一个一致性读视图 `consistent read view` ,主要包含了当前系统**未提交的事务列表** `TRX_IDs {TRX_ID_1, TRX_ID_2, ...}`,还有该列表的最小值 `TRX_ID_MIN` 和 `TRX_ID_MAX`。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715135809.png) - -这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能: - -1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的; -2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的; -3. 如果落在黄色部分,那就包括两种情况 - a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见; - b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。 - -在进行 `SELECT` 操作时,根据数据行快照的 `TRX_ID` 与 `TRX_ID_MIN` 和 `TRX_ID_MAX` 之间的关系,从而判断数据行快照是否可以使用: - -- `TRX_ID` < `TRX_ID_MIN`,表示该数据行快照时在当前所有未提交事务之前进行更改的,因此可以使用。 -- `TRX_ID` > `TRX_ID_MAX`,表示该数据行快照是在事务启动之后被更改的,因此不可使用。 -- `TRX_ID_MIN` <= `TRX_ID` <= `TRX_ID_MAX`,需要根据隔离级别再进行判断: - - 提交读:如果 `TRX_ID` 在 `TRX_IDs` 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。 - - 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。 - -在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。 - -### 快照读与当前读 - -快照读 - -MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。 - -```sql -SELECT * FROM table ...; -``` - -当前读 - -MVCC 其它会对数据库进行修改的操作(INSERT、UPDATE、DELETE)需要进行加锁操作,从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的加锁操作。 - -```sql -INSERT; -UPDATE; -DELETE; -``` - -在进行 SELECT 操作时,可以强制指定进行加锁操作。以下第一个语句需要加 S 锁,第二个需要加 X 锁。 - -```sql -SELECT * FROM table WHERE ? lock in share mode; -SELECT * FROM table WHERE ? for update; -``` - -## 行锁 - -行锁的具体实现算法有三种:record lock、gap lock 以及 next-key lock。 - -- `Record Lock` - **行锁对索引项加锁,若没有索引则使用表锁**。 -- `Gap Lock` - **对索引项之间的间隙加锁**。锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15:`SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;`。在 MySQL 中,gap lock 默认是开启的,即 `innodb_locks_unsafe_for_binlog` 参数值是 disable 的,且 MySQL 中默认的是 RR 事务隔离级别。 -- `Next-key lock` -它是 `Record Lock` 和 `Gap Lock` 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。它锁定一个前开后闭区间。 - -只在可重复读或以上隔离级别下的特定操作才会取得 gap lock 或 next-key lock。在 `Select`、`Update` 和 `Delete` 时,除了基于唯一索引的查询之外,其它索引查询时都会获取 gap lock 或 next-key lock,即锁住其扫描的范围。主键索引也属于唯一索引,所以主键索引是不会使用 gap lock 或 next-key lock。 - -MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题**。在可重复读(`REPEATABLE READ`)隔离级别下,使用 **MVCC + Next-Key 锁** 可以解决幻读问题。 - -索引分为主键索引和非主键索引两种,如果一条 SQL 语句操作了主键索引,MySQL 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL 会先锁定该非主键索引,再锁定相关的主键索引。在 `UPDATE`、`DELETE` 操作时,MySQL 不仅锁定 `WHERE` 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 `next-key lock`。 - -当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,`InnoDB` 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。 - -## 参考资料 - -- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) -- [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md) -- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) -- [使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" deleted file mode 100644 index 807f082b30..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\347\264\242\345\274\225.md" +++ /dev/null @@ -1,411 +0,0 @@ ---- -title: Mysql 索引 -date: 2020-07-16 11:14:07 -categories: - - 数据库 - - 关系型数据库 - - Mysql -tags: - - 数据库 - - 关系型数据库 - - Mysql - - 索引 -permalink: /pages/fcb19c/ ---- - -# Mysql 索引 - -> 索引是提高 MySQL 查询性能的一个重要途径,但过多的索引可能会导致过高的磁盘使用率以及过高的内存占用,从而影响应用程序的整体性能。应当尽量避免事后才想起添加索引,因为事后可能需要监控大量的 SQL 才能定位到问题所在,而且添加索引的时间肯定是远大于初始添加索引所需要的时间,可见索引的添加也是非常有技术含量的。 -> -> 接下来将向你展示一系列创建高性能索引的策略,以及每条策略其背后的工作原理。但在此之前,先了解与索引相关的一些算法和数据结构,将有助于更好的理解后文的内容。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715172009.png) - -## 索引简介 - -**索引是数据库为了提高查找效率的一种数据结构**。 - -索引对于良好的性能非常关键,在数据量小且负载较低时,不恰当的索引对于性能的影响可能还不明显;但随着数据量逐渐增大,性能则会急剧下降。因此,索引优化应该是查询性能优化的最有效手段。 - -### 索引的优缺点 - -B 树是最常见的索引,按照顺序存储数据,所以 Mysql 可以用来做 `ORDER BY` 和 `GROUP BY` 操作。因为数据是有序的,所以 B 树也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。 - -✔ 索引的优点: - -- **索引大大减少了服务器需要扫描的数据量**,从而加快检索速度。 -- **索引可以帮助服务器避免排序和临时表**。 -- **索引可以将随机 I/O 变为顺序 I/O**。 -- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。**使用索引可以减少访问的行数,从而减少锁的竞争,提高并发**。 -- 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。 - -❌ 索引的缺点: - -- **创建和维护索引要耗费时间**,这会随着数据量的增加而增加。 -- **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 -- **写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低**。 - -### 何时使用索引 - -> 索引能够轻易将查询性能提升几个数量级。 - -✔ 什么情况**适用**索引: - -- **频繁读操作( `SELECT` )** -- **表的数据量比较大**。 -- **列名经常出现在 `WHERE` 或连接(`JOIN`)条件中**。 - -❌ 什么情况**不适用**索引: - -- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` ),也就意味着需要更新索引。 -- **列名不经常出现在 `WHERE` 或连接(`JOIN`)条件中**,也就意味着索引会经常无法命中,没有意义,还增加空间开销。 -- **非常小的表**,对于非常小的表,大部分情况下简单的全表扫描更高效。 -- **特大型的表**,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 - -## 索引的数据结构 - -在 Mysql 中,索引是在存储引擎层而不是服务器层实现的。所以,并没有统一的索引标准;不同存储引擎的索引的数据结构也不相同。 - -### 数组 - -数组是用连续的内存空间来存储数据,并且支持随机访问。 - -有序数组可以使用二分查找法,其时间复杂度为 `O(log n)`,无论是等值查询还是范围查询,都非常高效。 - -但数组有两个重要限制: - -- 数组的空间大小固定,如果要扩容只能采用复制数组的方式。 -- 插入、删除时间复杂度为 `O(n)`。 - -这意味着,如果使用数组作为索引,如果要保证数组有序,其更新操作代价高昂。 - -### 哈希索引 - -哈希表是一种以键 - 值(key-value)对形式存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value。 - -**哈希表** 使用 **哈希函数** 组织数据,以支持快速插入和搜索的数据结构。哈希表的本质是一个数组,其思路是:使用 Hash 函数将 Key 转换为数组下标,利用数组的随机访问特性,使得我们能在 `O(1)` 的时间代价内完成检索。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220320201844.png) - -有两种不同类型的哈希表:**哈希集合** 和 **哈希映射**。 - -- **哈希集合** 是集合数据结构的实现之一,用于存储非重复值。 -- **哈希映射** 是映射 数据结构的实现之一,用于存储键值对。 - -哈希索引基于哈希表实现,**只适用于等值查询**。对于每一行数据,哈希索引都会将所有的索引列计算一个哈希码(`hashcode`),哈希码是一个较小的值。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。 - -在 Mysql 中,只有 Memory 存储引擎显示支持哈希索引。 - -✔ 哈希索引的**优点**: - -- 因为索引数据结构紧凑,所以**查询速度非常快**。 - -❌ 哈希索引的**缺点**: - -- 哈希索引值包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响不大。 -- **哈希索引数据不是按照索引值顺序存储的**,所以**无法用于排序**。 -- 哈希索引**不支持部分索引匹配查找**,因为哈希索引时使用索引列的全部内容来进行哈希计算的。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 -- 哈希索引**只支持等值比较查询**,包括 `=`、`IN()`、`<=>`;不支持任何范围查询,如 `WHERE price > 100`。 -- 哈希索引有**可能出现哈希冲突** - - 出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 - - 如果哈希冲突多的话,维护索引的代价会很高。 - -> 因为种种限制,所以哈希索引只适用于特定的场合。而一旦使用哈希索引,则它带来的性能提升会非常显著。 - -### B 树索引 - -通常我们所说的索引是指`B-Tree`索引,它是目前关系型数据库中查找数据最为常用和有效的索引,大多数存储引擎都支持这种索引。使用`B-Tree`这个术语,是因为 MySQL 在`CREATE TABLE`或其它语句中使用了这个关键字,但实际上不同的存储引擎可能使用不同的数据结构,比如 InnoDB 就是使用的`B+Tree`。 - -`B+Tree`中的 B 是指`balance`,意为平衡。需要注意的是,B+树索引并不能找到一个给定键值的具体行,它找到的只是被查找数据行所在的页,接着数据库会把页读入到内存,再在内存中进行查找,最后得到要查找的数据。 - -#### 二叉搜索树 - -二叉搜索树的特点是:每个节点的左儿子小于父节点,父节点又小于右儿子。其查询时间复杂度是 $$O(log(N))$$。 - -当然为了维持 $$O(log(N))$$ 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 $$O(log(N))$$。 - -随着数据库中数据的增加,索引本身大小随之增加,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘 I/O 消耗,相对于内存存取,I/O 存取的消耗要高几个数量级。可以想象一下一棵几百万节点的二叉树的深度是多少?如果将这么大深度的一颗二叉树放磁盘上,每读取一个节点,需要一次磁盘的 I/O 读取,整个查找的耗时显然是不能够接受的。那么如何减少查找过程中的 I/O 存取次数? - -一种行之有效的解决方法是减少树的深度,将**二叉树变为 N 叉树**(多路搜索树),而 **B+ 树就是一种多路搜索树**。 - -#### B+ 树 - -B+ 树索引适用于**全键值查找**、**键值范围查找**和**键前缀查找**,其中键前缀查找只适用于最左前缀查找。 - -理解`B+Tree`时,只需要理解其最重要的两个特征即可: - -- 第一,所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 -- 其次,所有的叶子节点由指针连接。如下图为简化了的`B+Tree`。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200304235424.jpg) - -根据叶子节点的内容,索引类型分为主键索引和非主键索引。 - -- **聚簇索引(clustered)**:又称为主键索引,其叶子节点存的是整行数据。因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。**InnoDB 的聚簇索引实际是在同一个结构中保存了 B 树的索引和数据行**。 -- 非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为**二级索引(secondary)**。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于 249 个。 - -**聚簇表示数据行和相邻的键值紧凑地存储在一起,因为数据紧凑,所以访问快**。因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。 - -**聚簇索引和非聚簇索引的查询有什么区别** - -- 如果语句是 `select * from T where ID=500`,即聚簇索引查询方式,则只需要搜索 ID 这棵 B+ 树; -- 如果语句是 `select * from T where k=5`,即非聚簇索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为**回表**。 - -也就是说,**基于非聚簇索引的查询需要多扫描一棵索引树**。因此,我们在应用中应该尽量使用主键查询。 - -**显然,主键长度越小,非聚簇索引的叶子节点就越小,非聚簇索引占用的空间也就越小。** - -自增主键是指自增列上定义的主键,在建表语句中一般是这么定义的: NOT NULL PRIMARY KEY AUTO_INCREMENT。从性能和存储空间方面考量,自增主键往往是更合理的选择。有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的: - -- 只有一个索引; -- 该索引必须是唯一索引。 - -由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。 - -这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。 - -### 全文索引 - -MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 - -全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。 - -InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 - -### 空间数据索引 - -MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 - -必须使用 GIS 相关的函数来维护数据。 - -## 索引的类型 - -主流的关系型数据库一般都支持以下索引类型: - -### 主键索引(`PRIMARY`) - -主键索引:一种特殊的唯一索引,不允许有空值。一个表只能有一个主键(在 InnoDB 中本质上即聚簇索引),一般是在建表的时候同时创建主键索引。 - -```sql -CREATE TABLE `table` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - ... - PRIMARY KEY (`id`) -) -``` - -### 唯一索引(`UNIQUE`) - -唯一索引:**索引列的值必须唯一,但允许有空值**。如果是组合索引,则列值的组合必须唯一。 - -```sql -CREATE TABLE `table` ( - ... - UNIQUE indexName (title(length)) -) -``` - -### 普通索引(`INDEX`) - -普通索引:最基本的索引,没有任何限制。 - -```sql -CREATE TABLE `table` ( - ... - INDEX index_name (title(length)) -) -``` - -### 全文索引(`FULLTEXT`) - -全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。 - -全文索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 WHERE 语句的参数匹配。全文索引配合 `match against` 操作使用,而不是一般的 WHERE 语句加 LIKE。它可以在 `CREATE TABLE`,`ALTER TABLE` ,`CREATE INDEX` 使用,不过目前只有 `char`、`varchar`,`text` 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用 `CREATE INDEX` 创建全文索引,要比先为一张表建立全文索引然后再将数据写入的速度快很多。 - -```sql -CREATE TABLE `table` ( - `content` text CHARACTER NULL, - ... - FULLTEXT (content) -) -``` - -### 联合索引 - -组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。 - -```sql -CREATE TABLE `table` ( - ... - INDEX index_name (title(length), title(length), ...) -) -``` - -## 索引的策略 - -假设有以下表: - -```sql -CREATE TABLE `t` ( - `id` int(11) NOT NULL, - `city` varchar(16) NOT NULL, - `name` varchar(16) NOT NULL, - `age` int(11) NOT NULL, - `addr` varchar(128) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `city` (`city`) -) ENGINE=InnoDB; -``` - -### 索引基本原则 - -- **索引不是越多越好,不要为所有列都创建索引**。要考虑到索引的维护代价、空间占用和查询时回表的代价。索引一定是按需创建的,并且要尽可能确保足够轻量。一旦创建了多字段的联合索引,我们要考虑尽可能利用索引本身完成数据查询,减少回表的成本。 -- 要**尽量避免冗余和重复索引**。 -- 要**考虑删除未使用的索引**。 -- **尽量的扩展索引,不要新建索引**。 -- **频繁作为 `WHERE` 过滤条件的列应该考虑添加索引**。 - -### 独立的列 - -**“独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数**。 - -**对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。** - -如果查询中的列不是独立的列,则数据库不会使用索引。 - -❌ 错误示例: - -```sql -SELECT actor_id FROM actor WHERE actor_id + 1 = 5; -SELECT ... WHERE TO_DAYS(current_date) - TO_DAYS(date_col) <= 10; -``` - -### 覆盖索引 - -**覆盖索引是指,索引上的信息足够满足查询请求,不需要回表查询数据。** - -【示例】范围查询 - -```sql -create table T ( -ID int primary key, -k int NOT NULL DEFAULT 0, -s varchar(16) NOT NULL DEFAULT '', -index k(k)) -engine=InnoDB; - -insert into T values(100,1, 'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg'); - -select * from T where k between 3 and 5 -``` - -需要执行几次树的搜索操作,会扫描多少行? - -1. 在 k 索引树上找到 k=3 的记录,取得 ID = 300; -2. 再到 ID 索引树查到 ID=300 对应的 R3; -3. 在 k 索引树取下一个值 k=5,取得 ID=500; -4. 再回到 ID 索引树查到 ID=500 对应的 R4; -5. 在 k 索引树取下一个值 k=6,不满足条件,循环结束。 - -在这个过程中,**回到主键索引树搜索的过程,我们称为回表**。可以看到,这个查询过程读了 k 索引树的 3 条记录(步骤 1、3 和 5),回表了两次(步骤 2 和 4)。 - -如果执行的语句是 select ID from T where k between 3 and 5,这时只需要查 ID 的值,而 ID 的值已经在 k 索引树上了,因此可以直接提供查询结果,不需要回表。索引包含所有需要查询的字段的值,称为覆盖索引。 - -**由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。** - -### 使用索引来排序 - -Mysql 有两种方式可以生成排序结果:通过排序操作;或者按索引顺序扫描。 - -**索引最好既满足排序,又用于查找行**。这样,就可以通过命中覆盖索引直接将结果查出来,也就不再需要排序了。 - -这样整个查询语句的执行流程就变成了: - -1. 从索引 (city,name,age) 找到第一个满足 city='杭州’条件的记录,取出其中的 city、name 和 age 这三个字段的值,作为结果集的一部分直接返回; -2. 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回; -3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。 - -### 前缀索引 - -有时候需要索引很长的字符列,这会让索引变得大且慢。 - -这时,可以使用前缀索引,即只索引开始的部分字符,这样可以**大大节约索引空间**,从而**提高索引效率**。但这样也**会降低索引的选择性**。对于 `BLOB`/`TEXT`/`VARCHAR` 这种文本类型的列,必须使用前缀索引,因为数据库往往不允许索引这些列的完整长度。 - -**索引的选择性**是指:不重复的索引值和数据表记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。如果存在多条命中前缀索引的情况,就需要依次扫描,直到最终找到正确记录。 - -**使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。** - -那么,如何确定前缀索引合适的长度呢? - -可以使用下面这个语句,算出这个列上有多少个不同的值: - -```sql -select count(distinct email) as L from SUser; -``` - -然后,依次选取不同长度的前缀来看这个值,比如我们要看一下 4~7 个字节的前缀索引,可以用这个语句: - -```sql -select - count(distinct left(email,4))as L4, - count(distinct left(email,5))as L5, - count(distinct left(email,6))as L6, - count(distinct left(email,7))as L7, -from SUser; -``` - -当然,**使用前缀索引很可能会损失区分度**,所以你需要预先设定一个可以接受的损失比例,比如 5%。然后,在返回的 L4~L7 中,找出不小于 L \* 95% 的值,假设这里 L6、L7 都满足,你就可以选择前缀长度为 6。 - -此外,**`order by` 无法使用前缀索引,无法把前缀索引用作覆盖索引**。 - -### 最左前缀匹配原则 - -不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。 - -MySQL 会一直向右匹配直到遇到范围查询 `(>,<,BETWEEN,LIKE)` 就停止匹配。 - -- 索引可以简单如一个列(a),也可以复杂如多个列(a, b, c, d),即**联合索引**。 -- 如果是联合索引,那么 key 也由多个列组成,同时,索引只能用于查找 key 是否**存在(相等)**,遇到范围查询(>、<、between、like 左匹配)等就**不能进一步匹配**了,后续退化为线性查找。 -- 因此,**列的排列顺序决定了可命中索引的列数**。 - -**不要为每个列都创建独立索引**。 - -**将选择性高的列或基数大的列优先排在多列索引最前列**。但有时,也需要考虑 `WHERE` 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。 - -例如:`a = 1 and b = 2 and c > 3 and d = 4`,如果建立(a,b,c,d)顺序的索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整。 - -让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 - -例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 - -```sql -SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, -COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, -COUNT(*) -FROM payment; -``` - -```batch - staff_id_selectivity: 0.0001 -customer_id_selectivity: 0.0373 - COUNT(*): 16049 -``` - -### = 和 in 可以乱序 - -**不需要考虑 `=`、`IN` 等的顺序**,Mysql 会自动优化这些条件的顺序,以匹配尽可能多的索引列。 - -【示例】如有索引 (a, b, c, d),查询条件 `c > 3 and b = 2 and a = 1 and d < 4` 与 `a = 1 and c > 3 and b = 2 and d < 4` 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c、d。 - -## 索引最佳实践 - -创建了索引,并非一定有效。比如不满足前缀索引、最左前缀匹配原则、查询条件涉及函数计算等情况都无法使用索引。此外,即使 SQL 本身符合索引的使用条件,MySQL 也会通过评估各种查询方式的代价,来决定是否走索引,以及走哪个索引。 - -因此,在尝试通过索引进行 SQL 性能优化的时候,务必通过执行计划(`EXPLAIN`)或实际的效果来确认索引是否能有效改善性能问题,否则增加了索引不但没解决性能问题,还增加了数据库增删改的负担。如果对 EXPLAIN 给出的执行计划有疑问的话,你还可以利用 `optimizer_trace` 查看详细的执行计划做进一步分析。 - -## 参考资料 - -- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) -- [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\351\224\201.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\351\224\201.md" new file mode 100644 index 0000000000..6913503797 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/05.Mysql\351\224\201.md" @@ -0,0 +1,540 @@ +--- +icon: logos:mysql +title: Mysql 锁 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202310162345947.png +date: 2020-09-07 07:54:19 +order: 05 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 锁 + - 读写锁 + - 悲观锁 + - 乐观锁 +permalink: /pages/f1f151/ +--- + +# Mysql 锁 + +> 不同存储引擎对于锁的支持粒度是不同的,由于 InnoDB 是 Mysql 的默认存储引擎,所以本文以 InnoDB 对于锁的支持进行阐述。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310162345947.png) + +## 锁的分类 + +为了解决并发一致性问题,Mysql 支持了很多种锁来实现不同程度的隔离性,以保证数据的安全性。 + +### 独享锁和共享锁 + +InnoDB 实现标准行级锁定,根据是否独享资源,可以把锁分为两类: + +- **独享锁(Exclusive)**,简写为 X 锁,又称为“**写锁**”、“**排它锁**”。 + - 独享锁锁定的数据只允许进行锁定操作的事务使用,其他事务无法对已锁定的数据进行查询或修改。 + - 使用方式:`SELECT ... FOR UPDATE;` +- **共享锁(Shared)**,简写为 S 锁,又称为“**读锁**”。 + - 共享锁锁定的资源可以被其他用户读取,但不能修改。在进行 `SELECT` 的时候,会将对象进行共享锁锁定,当数据读取完毕之后,就会释放共享锁,这样就可以保证数据在读取时不被修改。 + - 使用方式:`SELECT ... LOCK IN SHARE MODE;` + +> 为什么要引入读写锁机制? + +实际上,读写锁是一种通用的锁机制,并非 Mysql 的专利。在很多软件领域,都存在读写锁机制。 + +因为读操作本身是线程安全的,而一般业务往往又是读多写少的情况。因此,如果对读操作进行互斥,是不必要的,并且会大大降低并发访问效率。正式为了应对这种问题,产生了读写锁机制。 + +读写锁的特点是:**读读不互斥**、**读写互斥**、**写写互斥**。简言之:**只要存在写锁,其他事务就不能做任何操作**。 + +> 注:InnoDB 下的行锁、间隙锁、next-key 锁统统属于独享锁。 + +### 悲观锁和乐观锁 + +基于加锁方式分类,Mysql 可以分为悲观锁和乐观锁。 + +- **悲观锁** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作 + - 在查询完数据的时候就把事务锁起来,直到提交事务(`COMMIT`) + - 实现方式:**使用数据库中的锁机制**。 +- **乐观锁** - 假设最好的情况——每次访问数据时,都假设数据不会被其他线程修改,不必加锁。只在更新的时候,判断一下在此期间是否有其他线程更新该数据。 + - 实现方式:**更新数据时,先使用版本号机制或 CAS 算法检查数据是否被修改**。 + +> 为什么要引入乐观锁? + +乐观锁也是一种通用的锁机制,在很多软件领域,都存在乐观锁机制。 + +**锁,意味着互斥,意味着阻塞。在高并发场景下,锁越多,阻塞越多,势必会拉低并发性能**。那么,为了提高并发度,能不能尽量不加锁呢? + +乐观锁,顾名思义,就是假设最好的情况——每次访问数据时,都假设数据不会被其他线程修改,不必加锁。虽然不加锁,但不意味着什么都不做,而是在更新的时候,判断一下在此期间是否有其他线程更新该数据。乐观锁最常见的实现方式,是使用版本号机制或 CAS 算法(Compare And Swap)去实现。 + +- 乐观锁的**优点**是:减少锁竞争,提高并发度。 + +- 乐观锁的**缺点**是: + - **存在 ABA 问题**。所谓的 ABA 问题是指在并发编程中,如果一个变量初次读取的时候是 A 值,它的值被改成了 B,然后又其他线程把 B 值改成了 A,而另一个早期线程在对比值时会误以为此值没有发生改变,但其实已经发生变化了 + - 如果乐观锁所检查的数据存在大量锁竞争,会由于**不断循环重试,产生大量的 CPU 开销**。 + +【示例】Mysql 乐观锁示例 + +假设,order 表中有一个字段 status,表示订单状态:status 为 1 代表订单未支付;status 为 2 代表订单已支付。现在,要将 id 为 1 的订单状态置为已支付,则操作如下: + +```sql +select status, version from order where id=#{id} + +update order +set status=2, version=version+1 +where id=#{id} and version=#{version}; +``` + +> 乐观锁更多详情可以参考:[使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) + +### 全局锁、表级锁、行级锁 + +前文提到了,**锁,意味着互斥,意味着阻塞。在高并发场景下,锁越多,阻塞越多,势必会拉低并发性能**。在不得不加锁的情况下,显然,加锁的范围越小,锁竞争的发生频率就越小,系统的并发程度就越高。但是,加锁也需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销,**锁粒度越小,系统的锁操作开销就越大**。因此,在选择锁粒度时,也需要在锁开销和并发程度之间做一个权衡。 + +根据加锁的范围,MySQL 的锁大致可以分为: + +- **全局锁** - **“全局锁”会锁定整个数据库**。 +- **表级锁(table lock)** - **“表级锁”锁定整张表**。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。表级锁有: + - **表锁** - 表锁就是对数据表进行锁定,锁定粒度很大,同时发生锁冲突的概率也会较高,数据访问的并发度低。不过好处在于对锁的使用开销小,加锁会很快。表锁一般是在数据库引擎不支持行锁的时候才会被用到的。 + - **元数据锁(MDL)** - MDL 不需要显式使用,在访问一个表的时候会被自动加上。 + - **意向锁(Intention Lock)** + - **自增锁(AUTO-INC)** +- **行级锁(row lock)** - **“行级锁”锁定指定的行记录**。这样其它线程还是可以对同一个表中的其它行记录进行操作。行级锁有: + - **记录锁(Record Lock)** + - **间隙锁(Gap Lock)** + - **临键锁(Next-Key Lock)** + - **插入意向锁** + +以上各种加锁粒度,在不同存储引擎中的支持情况并不相同。如:InnoDB 支持全局锁、表级锁、行级锁;而 MyISAM 只支持全局锁、表级锁。 + +每个层级的锁数量是有限制的,因为锁会占用内存空间,锁空间的大小是有限的。当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如 InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。 + +## 全局锁 + +**“全局锁”会锁定整个数据库**。 + +要给整个数据库加全局锁,可以执行以下命令: + +```sql +flush tables with read lock +``` + +执行命名后,整个库处于只读状态,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。 + +如果要释放全局锁,可以执行以下命令: + +```sql +unlock tables +``` + +此外,在客户端断开的时候会自动释放锁。 + +**全局锁的典型使用场景是,做全库逻辑备份。** + +官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数 `–single-transaction` 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。对于全部是 InnoDB 引擎的库,建议选择使用 `–single-transaction` 参数,对应用会更友好。如果有的表使用了不支持事务的引擎,那么备份就只能通过 FTWRL 方法。 + +## 表级锁 + +**“表级锁”会锁定整张表**。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。 + +### 表锁 + +表锁就是对数据表进行锁定,锁定粒度很大,同时发生锁冲突的概率也会较高,数据访问的并发度低。不过好处在于对锁的使用开销小,加锁会很快。 + +**表锁的语法是 `lock tables … read/write`**,示例如下: + +```sql +// 为 xxx 表加 MDL 读锁 +lock tables xxx read; + +// 为 xxx 表加 MDL 写锁 +lock tables xxx write; +``` + +与 FTWRL 类似,可以用 `unlock tables` 主动释放锁,也可以在客户端断开的时候自动释放。 + +表锁一般是在数据库引擎不支持行锁的时候才会被用到的。如果你发现应用程序里有 `lock tables` 这样的语句,需要追查一下,比较可能的情况是: + +- 要么是你的系统现在还在用 MyISAM 这类不支持事务的引擎,那要安排升级换引擎; +- 要么是你的引擎升级了,但是代码还没升级。我见过这样的情况,最后业务开发就是把 `lock tables` 和 `unlock tables` 改成 `begin` 和 `commit`,问题就解决了。 + +### 元数据锁(MDL) + +元数据锁,英文为 metadata lock,缩写为 MDL。MySQL 5.5 版本中引入了 MDL。MDL 的作用是,保证读写的正确性。**MDL 不需要显式使用,在访问一个表的时候会被自动加上**。 + +- 对一个表做“**增删改查**”操作的时候,加 **MDL 读锁**。读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。 +- 对一个表做“**结构变更**”操作的时候,加 **MDL 写锁**。读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。 + +**MDL 会直到事务提交才释放**。在做表结构变更的时候,一定要小心不要导致锁住线上查询和更新。 + +如果数据库有一个长事务(所谓的长事务,就是开启了事务,但是一直还没提交),那么在对表结构做变更操作的时候,可能会发生意想不到的事情,比如下面这个顺序的场景: + +1. 首先,线程 A 先启用了事务(但是一直不提交),然后执行一条 `SELECT` 语句,此时就先对该表加上 MDL 读锁; +2. 然后,线程 B 也执行了同样的 `SELECT` 语句,此时并不会阻塞,因为“读读”并不冲突; +3. 接着,线程 C 修改了表字段,此时由于线程 A 的事务并没有提交,也就是 MDL 读锁还在占用着,这时线程 C 就无法申请到 MDL 写锁,就会被阻塞, + +那么在线程 C 阻塞后,后续有对该表的 SELECT 语句,就都会被阻塞。如果此时有大量该表的 SELECT 语句的请求到来,就会有大量的线程被阻塞住,这时数据库的线程很快就会爆满了。 + +> 为什么线程 C 因为申请不到 MDL 写锁,而导致后续的申请读锁的查询操作也会被阻塞? + +这是因为申请 MDL 锁的操作会形成一个队列,队列中**写锁获取优先级高于读锁**,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。 + +所以为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。 + +### 意向锁(Intention Lock) + +InnoDB 支持不同粒度的锁定,允许行锁和表锁共存。当存在表级锁和行级锁的情况下,必须先申请意向锁,再获取行级锁。意向锁是表级锁,表示事务稍后需要对表中的行使用哪种类型的锁(共享或独享)。意向锁是 InnoDB 自动添加的,不需要用户干预。 + +意向锁有两种类型: + +- **意向共享锁(`IS`)** - 表示事务有意向对表中的行设置共享锁(`S`)。 + +- **意向独享锁(`IX`)** - 表示事务有意向对表中的行设置独享锁(`X`)。 + +比如 `SELECT ... FOR SHARE` 设置 `IS` 锁, `SELECT ... FOR UPDATE` 设置 `IX` 锁。 + +意向锁的规则如下: + +- 一个事务在获得某个数据行的共享锁(`S`)之前,必须先获得表的意向共享锁(`IS`)或者更强的锁; +- 一个事务在获得某个数据行的独享锁(`X`)之前,必须先获得表的意向独享锁(`IX`)。 + +也就是,当执行插入、更新、删除操作,需要先对表加上 `IX` 锁,然后对该记录加 `X` 锁。而快照读(普通的 `SELECT`)是不会加行级锁的,快照读是利用 MVCC 实现一致性读,是无锁的。 + +不过,`SELECT` 也是可以对记录加共享锁和独享锁的,具体方式如下: + +```sql +// 先在表上加上 IS 锁,然后对读取的记录加 S 锁 +select ... lock in share mode; + +// 当前读:先在表上加上 IX 锁,然后对读取的记录加 X 锁 +select ... for update; +``` + +**意向共享锁和意向独享锁是表级锁,不会和行级的共享锁和独享锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(`lock tables ... read`)和独享表锁(`lock tables ... write`)发生冲突**。 + +如果申请的锁与现有锁兼容,则锁申请成功;反之,则锁申请失败。锁申请失败的情况下,申请锁的事务会一直等待,直到存在冲突的锁被释放。如果存在与申请的锁相冲突的锁,并且该锁迟迟得不到释放,就会导致死锁。 + +> 为什么要引入意向锁? + +如果没有意向锁,那么加独享表锁时,就需要遍历表里所有记录,查看是否有记录存在独享锁,这样效率会很低。 + +有了意向锁,在对记录加独享锁前,会先加上表级别的意向独享锁。此时,如果需要加独享表锁,可以直接查该表是否有意向独享锁:如果有,就意味着表里已经有记录被加了独享锁。这样一来,就不用去遍历表里的记录了。 + +综上所述,**意向锁的目的是为了快速判断表里是否有记录被加锁**。 + +### 自增锁(AUTO-INC) + +表里的主键通常都会设置成自增的,这是通过对主键字段声明 `AUTO_INCREMENT` 属性实现的。之后可以在插入数据时,可以不指定主键的值,数据库会自动给主键赋值递增的值,这主要是通过 **AUTO-INC 锁**实现的。 + +AUTO-INC 锁是特殊的表级锁,锁**不是在一个事务提交后才释放,而是在执行完插入语句后就会立即释放**。 + +**在插入数据时,会加一个表级别的 AUTO-INC 锁**,然后为被 `AUTO_INCREMENT` 修饰的字段赋值递增的值,等插入语句执行完成后,才会把 AUTO-INC 锁释放掉。 + +一个事务在持有 AUTO-INC 锁的过程中,其他事务的如果要向该表插入语句都会被阻塞,从而保证插入数据时,被 `AUTO_INCREMENT` 修饰的字段的值是连续递增的。但是,AUTO-INC 锁再对大量数据进行插入的时候,会影响插入性能,因为另一个事务中的插入会被阻塞。 + +因此, 在 MySQL 5.1.22 版本开始,InnoDB 存储引擎提供了一种**轻量级的锁**来实现自增。一样也是在插入数据的时候,会为被 `AUTO_INCREMENT` 修饰的字段加上轻量级锁,**然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁**。 + +InnoDB 存储引擎提供了个 `innodb_autoinc_lock_mode` 的系统变量,是用来控制选择用 AUTO-INC 锁,还是轻量级的锁。 + +- 当 `innodb_autoinc_lock_mode = 0`,就采用 AUTO-INC 锁,语句执行结束后才释放锁; +- 当 `innodb_autoinc_lock_mode = 2`,就采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。 +- 当 `innodb_autoinc_lock_mode = 1`: + - 普通 `insert` 语句,自增锁在申请之后就马上释放; + - 类似 `insert … select` 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放; + +以上模式中,`innodb_autoinc_lock_mode = 2` 是性能最高的方式,但是当搭配 binlog 的日志格式是 statement 一起使用的时候,在“主从复制的场景”中会发生**数据不一致的问题**。 + +## 行锁 + +MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB 是支持行锁的,这也是 MyISAM 被 InnoDB 替代的重要原因之一。 + +在 InnoDB 引擎中,**行锁是通过给索引上的索引项加锁来实现的**。**如果没有索引,`InnoDB` 将会通过隐藏的聚簇索引来对记录加锁**。此外,在 InnoDB 引擎中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。因此,如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 + +行锁的具体实现算法有三种:Record Lock、Gap Lock 以及 Next-Key Lock。 + +### 记录锁(Record Lock) + +**记录锁(Record Lock)锁定一个记录上的索引,而不是记录本身**。例如,执行 `SELECT value FROM t WHERE value BETWEEN 10 and 20 FOR UPDATE;` 后,会禁止任何其他事务插入、更新或删除 `t.value ` 值在 10 到 20 范围之内的数据,因为该范围内的所有现有值之间的间隙已被锁定。 + +记录锁始终锁定索引记录,即使表定义为没有索引。如果表没有设置索引,InnoDB 会自动创建一个隐藏的聚簇索引并使用该索引进行记录锁定。 + +Record Lock 是有 S 锁和 X 锁之分的: + +- 当一个事务对一条记录加了 S 型记录锁后,其他事务也可以继续对该记录加 S 型记录锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型记录锁(S 型与 X 锁不兼容); +- 当一个事务对一条记录加了 X 型记录锁后,其他事务既不可以对该记录加 S 型记录锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型记录锁(X 型与 X 锁不兼容)。 + +【示例】记录锁示例 + +> 注:测试环境的事务隔离级别为可重复级别 + +初始化数据 + +```sql +-- 创建表 +DROP TABLE IF EXISTS `t`; +CREATE TABLE `t` ( + `id` INT(10) NOT NULL AUTO_INCREMENT, + `value` INT(10) DEFAULT 0, + PRIMARY KEY (`id`) +) + ENGINE = InnoDB + DEFAULT CHARSET = `utf8`; + +-- 分别插入 id 为 1、10、20 的数据 +INSERT INTO `t`(`id`, `value`) VALUES (1, 1); +INSERT INTO `t`(`id`, `value`) VALUES (10, 10); +INSERT INTO `t`(`id`, `value`) VALUES (20, 20); +``` + +事务一、添加 X 型记录锁 + +```sql +-- 开启事务 +BEGIN; + +-- 对 id 为 1 的记录添加 X 型记录锁 +SELECT * FROM `t` WHERE `id` = 1 FOR UPDATE; + +-- 延迟 20 秒执行后续语句,保持锁定状态 +SELECT SLEEP(20); + +-- 释放锁 +COMMIT; +``` + +事务二、被锁定的行记录无法修改 + +```sql +-- 修改 id = 10 的行记录,正常执行 +UPDATE `t` SET `value` = 0 WHERE `id` = 10; + +-- 修改 id = 1 的行记录,由于 id = 1 被 X 型记录锁锁定,直到事务一释放锁,方能执行 +UPDATE `t` SET `value` = 0 WHERE `id` = 1; +``` + +### 间隙锁(Gap Lock) + +**间隙锁(Gap Lock)锁定索引之间的间隙,但是不包含索引本身**。 + +间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,它们彼此不冲突,不同事务可以在间隙上持有冲突锁,并不存在互斥关系。例如,事务 A 可以在某个间隙上持有 S 型间隙锁,而事务 B 在同一间隙上持有 X 型间隙锁。允许存在冲突间隙锁的原因是:如果从索引中清除记录,则必须合并不同事务在该记录上持有的间隙锁。 + +间隙锁只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。如果将事务隔离级别更改为 读已提交,则间隙锁定对搜索和索引扫描禁用,并且仅用于外键约束检查和重复键检查。 + +在 MySQL 中,间隙锁默认是开启的,即 `innodb_locks_unsafe_for_binlog` 参数值是 `disable` 的,且 MySQL 中默认的是 RR 事务隔离级别。 + +【示例】间隙锁示例 + +> 注:测试环境的事务隔离级别为可重复级别 + +初始化数据 + +```sql +-- 创建表 +DROP TABLE IF EXISTS `t`; +CREATE TABLE `t` ( + `id` INT(10) NOT NULL AUTO_INCREMENT, + `value` INT(10) DEFAULT 0, + PRIMARY KEY (`id`) +) + ENGINE = InnoDB + DEFAULT CHARSET = `utf8`; + +-- 分别插入 id 为 1、10、20 的数据 +INSERT INTO `t`(`id`, `value`) VALUES (1, 1); +INSERT INTO `t`(`id`, `value`) VALUES (10, 10); +INSERT INTO `t`(`id`, `value`) VALUES (20, 20); +``` + +事务一、添加间隙锁 + +```sql +-- 开启事务 +BEGIN; + +-- 对 id 为 1 的记录添加间隙锁 +SELECT * FROM `t` WHERE `id` BETWEEN 1 AND 10 FOR UPDATE; + +-- 延迟 20 秒执行后续语句,保持锁定状态 +SELECT SLEEP(20); + +-- 释放锁 +COMMIT; +``` + +事务二、被锁定范围内的行记录无法修改 + +```sql +-- 插入 id 为 1 到 10 范围之外的数据,正常执行 +INSERT INTO `t`(`id`, `value`) VALUES (15, 15); + +-- 更新 id 为 1 到 10 范围之外的数据,正常执行 +UPDATE `t` SET `value` = 0 WHERE `id` = 20; + +-- 插入 id 为 1 到 10 范围之内的数据,被阻塞 +INSERT INTO `t`(`id`, `value`) VALUES (5, 5); + +-- 更新 id 为 1 到 10 范围之内的数据,被阻塞 +UPDATE `t` SET `value` = 0 WHERE `id` = 1; +UPDATE `t` SET `value` = 0 WHERE `id` = 10; +``` + +### 临键锁(Next-Key Lock) + +**临键锁(Next-Key Lock)是记录锁和间隙锁的结合**,不仅锁定一个记录上的索引,也锁定索引之间的间隙(它锁定一个前开后闭区间)。 + +假设索引包含值 10、11、13 和 20,那么该索引可能的 Next-Key Lock 涵盖以下区间: + +``` +(-∞, 10] +(10, 11] +(11, 13] +(13, 20] +(20, +∞) +``` + +所以,Next-Key Lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。MVCC 不能解决幻读问题,**Next-Key 锁就是为了解决幻读问题而提出的**。在可重复读(`REPEATABLE READ`)隔离级别下,使用**MVCC + Next-Key 锁**可以解决幻读问题。 + +只有可重复读、串行化隔离级别下的特定操作才会取得间隙锁或 Next-Key Lock。在 `Select`、`Update` 和 `Delete` 时,除了基于唯一索引的查询之外,其它索引查询时都会获取间隙锁或 Next-Key Lock,即锁住其扫描的范围。主键索引也属于唯一索引,所以主键索引是不会使用间隙锁或 Next-Key Lock。 + +索引分为主键索引和非主键索引两种,如果一条 SQL 语句操作了主键索引,MySQL 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL 会先锁定该非主键索引,再锁定相关的主键索引。在 `UPDATE`、`DELETE` 操作时,MySQL 不仅锁定 `WHERE` 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 Next-Key Lock。 + +### 插入意向锁 + +插入意向锁不是意向锁,而是一种特殊的间隙锁。当一个事务试图插入一条记录时,需要判断插入位置是否已被其他事务加了间隙锁(临键锁(Next-Key Lock 也包含间隙锁)。如果有的话,插入操作就会发生**阻塞**,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻);在此期间,会生成一个**插入意向锁**,表明有事务想在某个区间插入新记录,但是现在处于等待状态。 + +假设存在值为 4 和 7 的索引记录。分别尝试插入值 5 和 6 的单独事务在获得插入行上的排他锁之前,每个事务都使用插入意向锁锁定 4 和 7 之间的间隙,但不要互相阻塞,因为行不冲突。 + +【示例】获取插入意向锁 + +初始化数据 + +```sql +mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB; +mysql> INSERT INTO child (id) values (90),(102); +``` + +事务 A 对 id 大于 100 的索引记录设置独享锁。独享锁包括了 id=102 之前的间隙锁: + +```sql +mysql> BEGIN; +mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE; ++-----+ +| id | ++-----+ +| 102 | ++-----+ +``` + +事务 B 将记录插入到间隙中。事务在等待获取独享锁时获取插入意向锁。 + +```sql +mysql> BEGIN; +mysql> INSERT INTO child (id) VALUES (101); +``` + +## 死锁 + +**“死锁”是指两个或多个事务竞争同一资源,并请求锁定对方占用的资源,从而导致恶性循环的现象**。 + +产生死锁的场景: + +- 当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。 + +- 多个事务同时锁定同一个资源时,也会产生死锁。 + +### 死锁示例 + +(1)数据初始化 + +```sql +-- 创建表 test +CREATE TABLE `test` ( + `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `value` INT(10) NOT NULL +); + +-- 数据初始化 +INSERT INTO `test` (`id`, `value`) VALUES (1, 1); +INSERT INTO `test` (`id`, `value`) VALUES (2, 2); +INSERT INTO `test` (`id`, `value`) VALUES (3, 3); +``` + +(2)两个事务严格按下表顺序执行,产生死锁 + +| 事务 A | 事务 B | +| --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| `BEGIN;` | `BEGIN;` | +| -- 查询 value = 4 的记录
`SELECT * FROM test WHERE value = 4 FOR UPDATE;`
-- 结果为空 | | +| | -- 查询 value = 5 的记录
`SELECT * FROM test WHERE value = 5 FOR UPDATE;`
-- 结果为空 | +| `INSERT INTO test (id, value) VALUES (4, 4);`
-- 锁等待中 | | +| | `INSERT INTO test (id, value) VALUES (5, 5);`
-- 锁等待中 | +| -- 由于死锁无法执行到此步骤
`COMMIT;` | -- 由于死锁无法执行到此步骤
`COMMIT;` | + +### 死锁是如何产生的 + +行锁的具体实现算法有三种:Record Lock、Gap Lock 以及 Next-Key Lock。Record Lock 是专门对索引项加锁;Gap Lock 是对索引项之间的间隙加锁;Next-Key Lock 则是前面两种的组合,对索引项以其之间的间隙加锁。 + +只有在可重复读或以上隔离级别下的特定操作才会取得 Gap Lock 或 Next-Key Lock,在 Select、Update 和 Delete 时,除了基于唯一索引的查询之外,其它索引查询时都会获取 Gap Lock 或 Next-Key Lock,即锁住其扫描的范围。主键索引也属于唯一索引,所以主键索引是不会使用 Gap Lock 或 Next-Key Lock。 + +在 MySQL 中,Gap Lock 默认是开启的,即 `innodb_locks_unsafe_for_binlog` 参数值是 `disable` 的,且 MySQL 中默认的是可重复读事务隔离级别。 + +当我们执行以下查询 SQL 时,由于 `value` 列为非唯一索引,此时又是 RR 事务隔离级别,所以 SELECT 的加锁类型为 Gap Lock,这里的 gap 范围是 (4,+∞)。 + +```sql +SELECT * FROM test where value = 4 for update; +``` + +执行查询 SQL 语句获取的 Gap Lock 并不会导致阻塞,而当我们执行以下插入 SQL 时,会在插入间隙上再次获取插入意向锁。插入意向锁其实也是一种 gap 锁,它与 Gap Lock 是冲突的,所以当其它事务持有该间隙的 Gap Lock 时,需要等待其它事务释放 Gap Lock 之后,才能获取到插入意向锁。 + +以上事务 A 和事务 B 都持有间隙 (4,+∞)的 gap 锁,而接下来的插入操作为了获取到插入意向锁,都在等待对方事务的 gap 锁释放,于是就造成了循环等待,导致死锁。 + +```sql +INSERT INTO `test` (`id`, `value`) VALUES (5, 5); +``` + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630153139.png) + +**另一个死锁场景** + +InnoDB 存储引擎的主键索引为聚簇索引,其它索引为辅助索引。如果使用辅助索引来更新数据库,就需要使用聚簇索引来更新数据库字段。如果两个更新事务使用了不同的辅助索引,或一个使用了辅助索引,一个使用了聚簇索引,就都有可能导致锁资源的循环等待。由于本身两个事务是互斥,也就构成了以上死锁的四个必要条件了。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630154606.png) + +出现死锁的步骤: + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200630154619.png) + +综上可知,在更新操作时,我们应该尽量使用主键来更新表字段,这样可以有效避免一些不必要的死锁发生。 + +### 避免死锁 + +死锁的四个必要条件:**互斥、占有且等待、不可强占用、循环等待**。只要系统发生死锁,这些条件必然成立,但是只要破坏任意一个条件就死锁就不会成立。由此可知,要想避免死锁,就要从这几个必要条件上去着手: + +- 更新表时,**尽量使用主键更新**,减少冲突; +- **避免长事务**,尽量将长事务拆解,可以降低与其它事务发生冲突的概率; +- **设置合理的锁等待超时参数**,我们可以通过 `innodb_lock_wait_timeout` 设置合理的等待超时阈值,特别是在一些高并发的业务中,我们可以尽量将该值设置得小一些,避免大量事务等待,占用系统资源,造成严重的性能开销。 +- 在编程中**尽量按照固定的顺序来处理数据库记录**,假设有两个更新操作,分别更新两条相同的记录,但更新顺序不一样,有可能导致死锁; +- 在允许幻读和不可重复读的情况下,尽量使用读已提交事务隔离级别,可以避免 Gap Lock 导致的死锁问题; +- 还可以使用其它的方式来代替数据库实现幂等性校验。例如,使用 Redis 以及 ZooKeeper 来实现,运行效率比数据库更佳。 + +### 解决死锁 + +当出现死锁以后,有两种策略: + +- **设置事务等待锁的超时时间**。这个超时时间可以通过参数 `innodb_lock_wait_timeout` 来设置。 +- **开启死锁检测**,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 `innodb_deadlock_detect` 设置为 `on`,表示开启这个逻辑。 + +在 InnoDB 中,`innodb_lock_wait_timeout` 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。但是,我直接把这个时间设置成一个很小的值,比如 1s,也是不可取的。当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。 + +所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 `innodb_deadlock_detect` 的默认值本身就是 on。为了解决死锁问题,不同数据库实现了各自的死锁检测和超时机制。InnoDB 的处理策略是:**将持有最少行级排它锁的事务进行回滚**。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。因此,死锁检测可能会耗费大量的 CPU。 + +## 参考资料 + +- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) +- [Mysql 官方文档之 InnoDB Locking](https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html) +- [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md) +- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79) +- [使用 mysql 乐观锁解决并发问题](https://www.cnblogs.com/laoyeye/p/8097684.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\351\253\230\345\217\257\347\224\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\351\253\230\345\217\257\347\224\250.md" new file mode 100644 index 0000000000..e05c059454 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\351\253\230\345\217\257\347\224\250.md" @@ -0,0 +1,269 @@ +--- +icon: logos:mysql +title: Mysql 高可用 +date: 2023-09-21 21:25:58 +order: 06 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 高可用 +permalink: /pages/083b48/ +--- + +# Mysql 高可用 + +## 复制 + +复制是解决系统高可用的常见手段。其思路就是:不要把鸡蛋都放在一个篮子里。 + +复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。主库和备库之 间可以有多种不同的组合方式。 + +MySQL 支持两种复制方式:基于行的复制和基于语句的复制。这两种方式都是通过在主库上记录 bin log、在备库重放日志的方式来实现异步的数据复制。这意味着:复制过程存在时延,这段时间内,主从数据可能不一致。 + +### 复制如何工作 + +在 Mysql 中,复制分为三个步骤,分别由三个线程完成: + +- **binlog dump 线程** - 主库上有一个特殊的 binlog dump 线程,负责将主服务器上的数据更改写入 binlog 中。 +- **I/O 线程** - 备库上有一个 I/O 线程,负责从主库上读取 binlog,并写入备库的中继日志(relay log)中。 +- **SQL 线程** - 备库上有一个 SQL 线程,负责读取中继日志(relay log)并重放其中的 SQL 语句。 + +
+ +
+ +这种架构实现了数据备份和数据同步的异步解耦。但这种架构也限制了复制的过程,其中最重要 的一点是在主库上并发运行的查询在备库只能串行化执行,因为只有一个 SQL 线程来重放 中继日志中的事件。 + +### 主备配置 + +假设需要配置一对 Mysql 主备节点,环境如下: + +- 主库节点:192.168.8.10 +- 备库节点:192.168.8.11 + +#### 主库上的操作 + +(1)修改配置并重启 + +执行 `vi /etc/my.cnf` ,添加如下配置: + +```ini +[mysqld] +server-id=1 +log_bin=/var/lib/mysql/binlog +``` + +- `server-id` - 服务器 ID 号。在主从架构中,每台机器的 ID 必须唯一。 +- `log_bin` - 同步的日志路径及文件名,一定注意这个目录要是 mysql 有权限写入的; + +修改后,重启 mysql 使配置生效: + +```shell +systemctl restart mysql +``` + +(2)创建用于同步的用户 + +进入 mysql 命令控制台: + +```sh +$ mysql -u root -p +Password: +``` + +执行以下 SQL: + +```sql +-- a. 创建 slave 用户 +CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '密码'; +-- 为 slave 赋予 REPLICATION SLAVE 权限 +GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%'; + +-- b. 或者,创建 slave 用户,并指定该用户能在任意主机上登录 +-- 如果有多个备库,又想让所有备库都使用统一的用户名、密码认证,可以考虑这种方式 +CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '密码'; +GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%'; + +-- 刷新授权表信息 +FLUSH PRIVILEGES; +``` + +> 注意:在 Mysql 8 中,默认密码验证不再是 `password`。所以在创建用户时,`create user 'username'@'%' identified by 'password';` 客户端是无法连接服务的。所以,需要加上 `IDENTIFIED WITH mysql_native_password BY 'password'` + +补充用户管理 SQL: + +```sql +-- 查看所有用户 +SELECT DISTINCT CONCAT('User: ''', user, '''@''', host, ''';') AS query +FROM mysql.user; + +-- 查看用户权限 +SHOW GRANTS FOR 'root'@'%'; + +-- 创建用户 +-- a. 创建 slave 用户,并指定该用户只能在主机 192.168.8.11 上登录 +CREATE USER 'slave'@'192.168.8.11' IDENTIFIED WITH mysql_native_password BY '密码'; +-- 为 slave 赋予 REPLICATION SLAVE 权限 +GRANT REPLICATION SLAVE ON *.* TO 'slave'@'192.168.8.11'; + +-- 删除用户 +DROP USER 'slave'@'192.168.8.11'; +``` + +(3)加读锁 + +为了主库与从库的数据保持一致,我们先为 mysql 加入读锁,使其变为只读。 + +```sql +mysql> FLUSH TABLES WITH READ LOCK; +``` + +(4)查看主库状态 + +```sql +mysql> show master status; ++------------------+----------+--------------+---------------------------------------------+-------------------+ +| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | ++------------------+----------+--------------+---------------------------------------------+-------------------+ +| mysql-bin.000001 | 4202 | | mysql,information_schema,performance_schema | | ++------------------+----------+--------------+---------------------------------------------+-------------------+ +1 row in set (0.00 sec) +``` + +> 注意:需要记录下 `File` 和 `Position`,后面会用到。 + +(5)导出 sql + +```shell +mysqldump -u root -p --all-databases --master-data > dbdump.sql +``` + +(6)解除读锁 + +```sql +mysql> UNLOCK TABLES; +``` + +(7)将 sql 远程传送到备库上 + +```shell +scp dbdump.sql root@192.168.8.11:/home +``` + +#### 备库上的操作 + +(1)修改配置并重启 + +执行 `vi /etc/my.cnf` ,添加如下配置: + +```ini +[mysqld] +server-id=2 +log_bin=/var/lib/mysql/binlog +``` + +- `server-id` - 服务器 ID 号。在主从架构中,每台机器的 ID 必须唯一。 +- `log_bin` - 同步的日志路径及文件名,一定注意这个目录要是 mysql 有权限写入的; + +修改后,重启 mysql 使配置生效: + +```shell +systemctl restart mysql +``` + +(2)导入 sql + +```shell +mysql -u root -p < /home/dbdump.sql +``` + +(3)在备库上建立与主库的连接 + +进入 mysql 命令控制台: + +```shell +$ mysql -u root -p +Password: +``` + +执行以下 SQL: + +```sql +-- 停止备库服务 +STOP SLAVE; + +-- 注意:MASTER_USER 和 +CHANGE MASTER TO +MASTER_HOST='192.168.8.10', +MASTER_USER='slave', +MASTER_PASSWORD='密码', +MASTER_LOG_FILE='binlog.000001', +MASTER_LOG_POS=4202; +``` + +- `MASTER_LOG_FILE` 和 `MASTER_LOG_POS` 参数要分别与 `show master status` 指令获得的 `File` 和 `Position` 属性值对应。 +- `MASTER_HOST` 是主库的 HOST。 +- `MASTER_USER` 和 `MASTER_PASSWORD` 是在主节点上注册的用户及密码。 + +(4)启动 slave 进程 + +```sql +mysql> start slave; +``` + +(5)查看主从同步状态 + +```sql +mysql> show slave status\G; +``` + +说明:如果以下两项参数均为 YES,说明配置正确。 + +- `Slave_IO_Running` +- `Slave_SQL_Running` + +(6)将备库设为只读 + +```sql +mysql> set global read_only=1; +mysql> set global super_read_only=1; +mysql> show global variables like "%read_only%"; ++-----------------------+-------+ +| Variable_name | Value | ++-----------------------+-------+ +| innodb_read_only | OFF | +| read_only | ON | +| super_read_only | ON | +| transaction_read_only | OFF | ++-----------------------+-------+ +``` + +> 注:设置 slave 服务器为只读,并不影响主从同步。 + +## 复制的原理 + +## 读写分离 + +主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 + +读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。 + +MySQL 读写分离能提高性能的原因在于: + +- 主从服务器负责各自的读和写,极大程度缓解了锁的争用; +- 从服务器可以配置 MyISAM 引擎,提升查询性能以及节约系统开销; +- 增加冗余,提高可用性。 + +
+ +
+ +## 参考资料 + +- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/07.Mysql\344\274\230\345\214\226.md" similarity index 58% rename from "source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" rename to "source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/07.Mysql\344\274\230\345\214\226.md" index 2339ac315c..b11daf843c 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/06.Mysql\346\200\247\350\203\275\344\274\230\345\214\226.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/07.Mysql\344\274\230\345\214\226.md" @@ -1,6 +1,8 @@ --- -title: Mysql 性能优化 +icon: logos:mysql +title: Mysql 优化 date: 2020-06-03 20:16:48 +order: 07 categories: - 数据库 - 关系型数据库 @@ -9,94 +11,148 @@ tags: - 数据库 - 关系型数据库 - Mysql - - 性能 + - 优化 permalink: /pages/396816/ --- -# Mysql 性能优化 +# Mysql 优化 -## 数据结构优化 +## 慢查询 -良好的逻辑设计和物理设计是高性能的基石。 +慢查询日志可以帮我们找到执行慢的 SQL。 -### 数据类型优化 +可以通过以下命令查看慢查询日志是否开启: -#### 数据类型优化基本原则 +```sql +mysql> show variables like '%slow_query_log'; ++----------------+-------+ +| Variable_name | Value | ++----------------+-------+ +| slow_query_log | ON | ++----------------+-------+ +1 row in set (0.02 sec) +``` -- **更小的通常更好** - 越小的数据类型通常会更快,占用更少的磁盘、内存,处理时需要的 CPU 周期也更少。 - - 例如:整型比字符类型操作代价低,因而会使用整型来存储 IP 地址,使用 `DATETIME` 来存储时间,而不是使用字符串。 -- **简单就好** - 如整型比字符型操作代价低。 - - 例如:很多软件会用整型来存储 IP 地址。 - - 例如:**`UNSIGNED` 表示不允许负值,大致可以使正数的上限提高一倍**。 -- **尽量避免 NULL** - 可为 NULL 的列会使得索引、索引统计和值比较都更复杂。 +启停慢查询日志开关: -#### 类型的选择 +```sql +# 开启慢查询日志 +mysql > set global slow_query_log='ON'; -- 整数类型通常是标识列最好的选择,因为它们很快并且可以使用 `AUTO_INCREMENT`。 +# 关闭慢查询日志 +mysql > set global slow_query_log='OFF'; +``` -- `ENUM` 和 `SET` 类型通常是一个糟糕的选择,应尽量避免。 -- 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 - - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 +查看慢查询的时间阈值: -### 表设计 +```sql +mysql> show variables like '%long_query_time%'; ++-----------------+-----------+ +| Variable_name | Value | ++-----------------+-----------+ +| long_query_time | 10.000000 | ++-----------------+-----------+ +1 row in set (0.02 sec) +``` -应该避免的设计问题: +设置慢查询的时间阈值: -- **太多的列** - 设计者为了图方便,将大量冗余列加入表中,实际查询中,表中很多列是用不到的。这种宽表模式设计,会造成不小的性能代价,尤其是 `ALTER TABLE` 非常耗时。 -- **太多的关联** - 所谓的实体 - 属性 - 值(EVA)设计模式是一个常见的糟糕设计模式。Mysql 限制了每个关联操作最多只能有 61 张表,但 EVA 模式需要许多自关联。 -- **枚举** - 尽量不要用枚举,因为添加和删除字符串(枚举选项)必须使用 `ALTER TABLE`。 -- 尽量避免 `NULL` +```sql +mysql > set global long_query_time = 3; +``` -### 范式和反范式 +MySQL 自带了一个 mysqldumpslow 工具,用于统计慢查询日志(这个工具是个 Perl 脚本,需要先安装好 Perl)。 -**范式化目标是尽量减少冗余,而反范式化则相反**。 +mysqldumpslow 命令的具体参数如下: -范式化的优点: +- `-s` - 采用 order 排序的方式,排序方式可以有以下几种。分别是 c(访问次数)、t(查询时间)、l(锁定时间)、r(返回记录)、ac(平均查询次数)、al(平均锁定时间)、ar(平均返回记录数)和 at(平均查询时间)。其中 at 为默认排序方式。 +- `-t` - 返回前 N 条数据 。 +- `-g` - 后面可以是正则表达式,对大小写不敏感。 -- 比反范式更节省空间 -- 更新操作比反范式快 -- 更少需要 `DISTINCT` 或 `GROUP BY` 语句 +比如想要按照查询时间排序,查看前两条 SQL 语句,可以执行如下命令: -范式化的缺点: +```shell +perl mysqldumpslow.pl -s t -t 2 "C:\ProgramData\MySQL\MySQL Server 8.0\Data\slow.log" +``` -- 通常需要关联查询。而关联查询代价较高,如果是分表的关联查询,代价更是高昂。 +## 执行计划(EXPLAIN) -在真实世界中,很少会极端地使用范式化或反范式化。实际上,应该权衡范式和反范式的利弊,混合使用。 +**“执行计划”是对 SQL 查询语句在数据库中执行过程的描述**。 如果要分析某条 SQL 的性能问题,通常需要先查看 SQL 的执行计划,排查每一步 SQL 执行是否存在问题。 -### 索引优化 +很多数据库都支持执行计划,Mysql 也不例外。在 Mysql 中,用户可以通过 `EXPLAIN` 命令查看优化器针对指定 SQL 生成的逻辑执行计划。 -> 索引优化应该是查询性能优化的最有效手段。 -> -> 如果想详细了解索引特性请参考:[Mysql 索引](https://github.com/dunwu/db-tutorial/blob/master/docs/sql/mysql/mysql-index.md) +【示例】Mysql 执行计划示例 -#### 何时使用索引 +```sql +mysql> explain select * from user_info where id = 2\G +*************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: user_info + partitions: NULL + type: const +possible_keys: PRIMARY + key: PRIMARY + key_len: 8 + ref: const + rows: 1 + filtered: 100.00 + Extra: NULL +1 row in set, 1 warning (0.00 sec) +``` -- 对于非常小的表,大部分情况下简单的全表扫描更高效。 -- 对于中、大型表,索引非常有效。 -- 对于特大型表,建立和使用索引的代价将随之增长。可以考虑使用分区技术。 -- 如果表的数量特别多,可以建立一个元数据信息表,用来查询需要用到的某些特性。 +执行计划返回结果参数说明: + +- `id` - SELECT 查询的标识符。每个 `SELECT` 都会自动分配一个唯一的标识符。 +- `select_type` - `SELECT` 查询的类型。 + - `SIMPLE` - 表示此查询不包含 `UNION` 查询或子查询。 + - `PRIMARY` - 表示此查询是最外层的查询。 + - `UNION` - 表示此查询是 `UNION` 的第二或随后的查询。 + - `DEPENDENT UNION` - `UNION` 中的第二个或后面的查询语句, 取决于外面的查询。 + - `UNION RESULT` - `UNION` 的结果。 + - `SUBQUERY` - 子查询中的第一个 `SELECT`。 + - `DEPENDENT SUBQUERY` - 子查询中的第一个 `SELECT`, 取决于外面的查询. 即子查询依赖于外层查询的结果。 +- `table` - 查询的是哪个表,如果给表起别名了,则显示别名。 +- `partitions` - 匹配的分区。 +- `type` - 表示从表中查询到行所执行的方式,查询方式是 SQL 优化中一个很重要的指标,执行效率由高到低依次为: + - `system`/`const` - 表中只有一行数据匹配。此时根据索引查询一次就能找到对应的数据。如果是 B+ 树索引,我们知道此时索引构造成了多个层级的树,当查询的索引在树的底层时,查询效率就越低。`const` 表示此时索引在第一层,只需访问一层便能得到数据。 + - `eq_ref` - 使用唯一索引扫描。常见于多表连接中使用主键和唯一索引作为关联条件。 + - `ref` - 非唯一索引扫描。还可见于唯一索引最左原则匹配扫描。 + - `range` - 索引范围扫描。比如 `<`,`>`,`between` 等操作。 + - `index` - 索引全表扫描。此时遍历整个索引树。 + - `ALL` - 表示全表扫描。需要遍历全表来找到对应的行。 +- `possible_keys` - 此次查询中可能选用的索引。 +- `key` - 此次查询中实际使用的索引。如果这一项为 `NULL`,说明没有使用索引。 +- `ref` - 哪个字段或常数与 key 一起被使用。 +- `rows` - 显示此查询一共扫描了多少行,这个是一个估计值。 +- `filtered` - 表示此查询条件所过滤的数据的百分比。 +- `extra` - 额外的信息。 + - `Using filesort` - 当查询语句中包含 `GROUP BY` 操作,而且无法利用索引完成排序操作的时候, 这时不得不选择相应的排序算法进行,甚至可能会通过文件排序,效率是很低的,所以要避免这种问题的出现。 + - `Using temporary` - 使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表,常见于排序 `ORDER BY` 和分组查询 `GROUP BY`。效率低,要避免这种问题的出现。 + - `Using index` - 所需数据只需在索引即可全部获得,不须要再到表中取数据,也就是使用了覆盖索引,避免了回表操作,效率不错。 -#### 索引优化策略 +> 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) -- **索引基本原则** - - 索引不是越多越好,不要为所有列都创建索引。 - - 要尽量避免冗余和重复索引。 - - 要考虑删除未使用的索引。 - - 尽量的扩展索引,不要新建索引。 - - 频繁作为 `WHERE` 过滤条件的列应该考虑添加索引。 -- **独立的列** - “独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数。 -- **前缀索引** - 索引很长的字符列,可以索引开始的部分字符,这样可以大大节约索引空间。 -- **最左匹配原则** - 将选择性高的列或基数大的列优先排在多列索引最前列。 -- **使用索引来排序** - 索引最好既满足排序,又用于查找行。这样,就可以使用索引来对结果排序。 -- `=`、`IN` 可以乱序 - 不需要考虑 `=`、`IN` 等的顺序 -- **覆盖索引** -- **自增字段作主键** +## optimizer trace + +在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。 + +如下代码所示,打开 optimizer_trace 后,再执行 SQL 就可以查询 information_schema.OPTIMIZER_TRACE 表查看执行计划了,最后可以关闭 optimizer_trace 功能: + +```sql +SET optimizer_trace="enabled=on"; +SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-01-24 05:00 +SELECT * FROM information_schema.OPTIMIZER_TRACE; +SET optimizer_trace="enabled=off"; +``` ## SQL 优化 +### SQL 优化基本思路 + 使用 `EXPLAIN` 命令查看当前 SQL 是否使用了索引,优化后,再通过执行计划(`EXPLAIN`)来查看优化效果。 -SQL 优化基本思路: +SQL 优化的基本思路: - **只返回必要的列** - 最好不要使用 `SELECT *` 语句。 @@ -104,111 +160,110 @@ SQL 优化基本思路: - **缓存重复查询的数据** - 应该考虑在客户端使用缓存,尽量不要使用 Mysql 服务器缓存(存在较多问题和限制)。 -- **使用索引来覆盖查询** +- **使用索引覆盖查询** -### 优化 `COUNT()` 查询 +### 优化分页 -`COUNT()` 有两种作用: +当需要分页操作时,通常会使用 `LIMIT` 加上偏移量的办法实现,同时加上合适的 `ORDER BY` 字句。**如果有对应的索引,通常效率会不错,否则,MySQL 需要做大量的文件排序操作**。 -- 统计某个列值的数量。统计列值时,要求列值是非 `NULL` 的,它不会统计 `NULL`。 -- 统计行数。 +一个常见的问题是当偏移量非常大的时候,比如:`LIMIT 1000000 20` 这样的查询,MySQL 需要查询 1000020 条记录然后只返回 20 条记录,前面的 1000000 条都将被抛弃,这样的代价非常高。 -**统计列值时,要求列值是非空的,它不会统计 NULL**。如果确认括号中的表达式不可能为空时,实际上就是在统计行数。最简单的就是当使用 `COUNT(*)` 时,并不是我们所想象的那样扩展成所有的列,实际上,它会忽略所有的列而直接统计行数。 +针对分页优化,有以下两种方案 -我们最常见的误解也就在这儿,在括号内指定了一列却希望统计结果是行数,而且还常常误以为前者的性能会更好。但实际并非这样,如果要统计行数,直接使用 `COUNT(*)`,意义清晰,且性能更好。 +(1)方案 - 延迟关联 -(1)简单优化 +优化这种查询一个最简单的办法就是尽可能的使用覆盖索引扫描,而不是查询所有的列。然后根据需要做一次关联查询再返回所有的列。对于偏移量很大时,这样做的效率会提升非常大。考虑下面的查询: ```sql -SELECT count(*) FROM world.city WHERE id > 5; +SELECT film_id,description FROM film ORDER BY title LIMIT 1000000,5; +``` -SELECT (SELECT count(*) FROM world.city) - count(*) -FROM world.city WHERE id <= 5; +如果这张表非常大,那么这个查询最好改成下面的样子: + +```sql +SELECT film.film_id,film.description +FROM film INNER JOIN ( + SELECT film_id FROM film ORDER BY title LIMIT 50,5 +) AS tmp USING(film_id); ``` -(2)使用近似值 +这里的延迟关联将大大提升查询效率,让 MySQL 扫描尽可能少的页面,获取需要访问的记录后在根据关联列回原表查询所需要的列。 -有时候某些业务场景并不需要完全精确的统计值,可以用近似值来代替,`EXPLAIN` 出来的行数就是一个不错的近似值,而且执行 `EXPLAIN` 并不需要真正地去执行查询,所以成本非常低。通常来说,执行 `COUNT()` 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL 层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用 Redis 这样的外部缓存系统。 +(2)方案 - 书签方式 + +有时候如果可以使用书签记录上次取数据的位置,那么下次就可以直接从该书签记录的位置开始扫描,这样就可以避免使用 `OFFSET`,比如下面的查询: -### 优化关联查询 +```sql +-- 原语句 +SELECT id FROM t LIMIT 1000000, 10; +-- 优化语句 +SELECT id FROM t WHERE id > 1000000 LIMIT 10; +``` -在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 `JOIN` 有更好的性能。 +其他优化的办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表中只包含主键列和需要做排序的列。 -如果确实需要使用关联查询的情况下,需要特别注意的是: +### 优化 JOIN -- **确保 `ON` 和 `USING` 字句中的列上有索引**。在创建索引的时候就要考虑到关联的顺序。当表 A 和表 B 用某列 column 关联的时候,如果优化器关联的顺序是 A、B,那么就不需要在 A 表的对应列上创建索引。没有用到的索引会带来额外的负担,一般来说,除非有其他理由,只需要在关联顺序中的第二张表的相应列上创建索引(具体原因下文分析)。 -- **确保任何的 `GROUP BY` 和 `ORDER BY` 中的表达式只涉及到一个表中的列**,这样 MySQL 才有可能使用索引来优化。 +优化子查询 -要理解优化关联查询的第一个技巧,就需要理解 MySQL 是如何执行关联查询的。当前 MySQL 关联执行的策略非常简单,它对任何的关联都执行**嵌套循环关联**操作,即先在一个表中循环取出单条数据,然后在嵌套循环到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为为止。然后根据各个表匹配的行,返回查询中需要的各个列。 +尽量使用 `JOIN` 语句来替代子查询。因为子查询是嵌套查询,而嵌套查询会新创建一张临时表,而临时表的创建与销毁会占用一定的系统资源以及花费一定的时间,同时对于返回结果集比较大的子查询,其对查询性能的影响更大。 -太抽象了?以上面的示例来说明,比如有这样的一个查询: +小表驱动大表 -```css -SELECT A.xx,B.yy -FROM A INNER JOIN B USING(c) -WHERE A.xx IN (5,6) -``` +JOIN 查询时,应该用小表驱动大表。因为 JOIN 时,MySQL 内部会先遍历驱动表,再去遍历被驱动表。 -假设 MySQL 按照查询中的关联顺序 A、B 来进行关联操作,那么可以用下面的伪代码表示 MySQL 如何完成这个查询: - -```ruby -outer_iterator = SELECT A.xx,A.c FROM A WHERE A.xx IN (5,6); -outer_row = outer_iterator.next; -while(outer_row) { - inner_iterator = SELECT B.yy FROM B WHERE B.c = outer_row.c; - inner_row = inner_iterator.next; - while(inner_row) { - output[inner_row.yy,outer_row.xx]; - inner_row = inner_iterator.next; - } - outer_row = outer_iterator.next; -} +比如 left join,左表就是驱动表,A 表小于 B 表,建立连接的次数就少,查询速度就被加快了。 + +```sql +select name from A left join B ; ``` -可以看到,最外层的查询是根据`A.xx`列来查询的,`A.c`上如果有索引的话,整个关联查询也不会使用。再看内层的查询,很明显`B.c`上如果有索引的话,能够加速查询,因此只需要在关联顺序中的第二张表的相应列上创建索引即可。 +适当冗余字段 -### 优化 `GROUP BY` 和 `DISTINCT` +增加冗余字段可以减少大量的连表查询,因为多张表的连表查询性能很低,所有可以适当的增加冗余字段,以减少多张表的关联查询,这是以空间换时间的优化策略 -Mysql 优化器会在内部处理的时候相互转化这两类查询。它们都**可以使用索引来优化,这也是最有效的优化方法**。 +避免 JOIN 太多表 -### 优化 `LIMIT` +《阿里巴巴 Java 开发手册》规定不要 join 超过三张表,第一 join 太多降低查询的速度,第二 join 的 buffer 会占用更多的内存。 -当需要分页操作时,通常会使用 `LIMIT` 加上偏移量的办法实现,同时加上合适的 `ORDER BY` 字句。**如果有对应的索引,通常效率会不错,否则,MySQL 需要做大量的文件排序操作**。 +如果不可避免要 join 多张表,可以考虑使用数据异构的方式异构到 ES 中查询。 -一个常见的问题是当偏移量非常大的时候,比如:`LIMIT 10000 20`这样的查询,MySQL 需要查询 10020 条记录然后只返回 20 条记录,前面的 10000 条都将被抛弃,这样的代价非常高。 +### 优化 UNION -优化这种查询一个最简单的办法就是尽可能的使用覆盖索引扫描,而不是查询所有的列。然后根据需要做一次关联查询再返回所有的列。对于偏移量很大时,这样做的效率会提升非常大。考虑下面的查询: +MySQL 执行 `UNION` 的策略是:先创建临时表,然后将各个查询结果填充到临时表中,最后再进行查询。很多优化策略在 `UNION` 查询中都会失效,因为它无法利用索引。 -```sql -SELECT film_id,description FROM film ORDER BY title LIMIT 50,5; -``` +最好将 `WHERE`、`LIMIT` 等子句下推到 `UNION` 的各个子查询中,以便优化器可以充分利用这些条件进行优化。 -如果这张表非常大,那么这个查询最好改成下面的样子: +此外,尽量使用 `UNION ALL`,避免使用 `UNION`。 -```sql -SELECT film.film_id,film.description -FROM film INNER JOIN ( - SELECT film_id FROM film ORDER BY title LIMIT 50,5 -) AS tmp USING(film_id); -``` +`UNION` 和 `UNION ALL` 都是将两个结果集合并为一个,**两个要联合的 SQL 语句字段个数必须一样,而且字段类型要“相容”(一致)**。 -这里的延迟关联将大大提升查询效率,让 MySQL 扫描尽可能少的页面,获取需要访问的记录后在根据关联列回原表查询所需要的列。 +- `UNION` 需要进行去重扫描,因此消息较低;而 `UNION ALL` 不会进行去重。 +- `UNION` 会按照字段的顺序进行排序;而 `UNION ALL` 只是简单的将两个结果合并就返回。 -有时候如果可以使用书签记录上次取数据的位置,那么下次就可以直接从该书签记录的位置开始扫描,这样就可以避免使用`OFFSET`,比如下面的查询: +### 优化 COUNT() 查询 -```objectivec -SELECT id FROM t LIMIT 10000, 10; -改为: -SELECT id FROM t WHERE id > 10000 LIMIT 10; -``` +`COUNT()` 有两种作用: -其他优化的办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表中只包含主键列和需要做排序的列。 +- 统计某个列值的数量。统计列值时,要求列值是非 `NULL` 的,它不会统计 `NULL`。 +- 统计行数。 -### 优化 UNION +**统计列值时,要求列值是非空的,它不会统计 NULL**。如果确认括号中的表达式不可能为空时,实际上就是在统计行数。最简单的就是当使用 `COUNT(*)` 时,并不是我们所想象的那样扩展成所有的列,实际上,它会忽略所有的列而直接统计行数。 + +我们最常见的误解也就在这儿,在括号内指定了一列却希望统计结果是行数,而且还常常误以为前者的性能会更好。但实际并非这样,如果要统计行数,直接使用 `COUNT(*)`,意义清晰,且性能更好。 + +(1)简单优化 + +```sql +SELECT count(*) FROM world.city WHERE id > 5; + +SELECT (SELECT count(*) FROM world.city) - count(*) +FROM world.city WHERE id <= 5; +``` -MySQL 总是通过创建并填充临时表的方式来执行 `UNION` 查询。因此很多优化策略在`UNION`查询中都没有办法很好的时候。经常需要手动将`WHERE`、`LIMIT`、`ORDER BY`等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。 +(2)使用近似值 -除非确实需要服务器去重,否则就一定要使用`UNION ALL`,如果没有`ALL`关键字,MySQL 会给临时表加上`DISTINCT`选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用 ALL 关键字,MySQL 总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。 +有时候某些业务场景并不需要完全精确的统计值,可以用近似值来代替,`EXPLAIN` 出来的行数就是一个不错的近似值,而且执行 `EXPLAIN` 并不需要真正地去执行查询,所以成本非常低。通常来说,执行 `COUNT()` 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL 层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用 Redis 这样的外部缓存系统。 ### 优化查询方式 @@ -251,71 +306,88 @@ SELECT * FROM tag_post WHERE tag_id=1234; SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); ``` -## 执行计划(`EXPLAIN`) +### 索引优化 -如何判断当前 SQL 是否使用了索引?如何检验修改后的 SQL 确实有优化效果? +通过索引覆盖查询,可以优化排序、分组。 -在 SQL 中,可以通过执行计划(`EXPLAIN`)分析 `SELECT` 查询效率。 +详情见 [Mysql 索引](https://dunwu.github.io/waterdrop/pages/fcb19c/) -```sql -mysql> explain select * from user_info where id = 2\G -*************************** 1. row *************************** - id: 1 - select_type: SIMPLE - table: user_info - partitions: NULL - type: const -possible_keys: PRIMARY - key: PRIMARY - key_len: 8 - ref: const - rows: 1 - filtered: 100.00 - Extra: NULL -1 row in set, 1 warning (0.00 sec) -``` +## 数据结构优化 -`EXPLAIN` 参数说明: - -- `id`: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符. -- `select_type` ⭐ :SELECT 查询的类型. - - `SIMPLE`:表示此查询不包含 UNION 查询或子查询 - - `PRIMARY`:表示此查询是最外层的查询 - - `UNION`:表示此查询是 UNION 的第二或随后的查询 - - `DEPENDENT UNION`:UNION 中的第二个或后面的查询语句, 取决于外面的查询 - - `UNION RESULT`:UNION 的结果 - - `SUBQUERY`:子查询中的第一个 SELECT - - `DEPENDENT SUBQUERY`: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果. -- `table`: 查询的是哪个表,如果给表起别名了,则显示别名。 -- `partitions`:匹配的分区 -- `type` ⭐:表示从表中查询到行所执行的方式,查询方式是 SQL 优化中一个很重要的指标,结果值从好到差依次是:system > const > eq_ref > ref > range > index > ALL。 - - `system`/`const`:表中只有一行数据匹配,此时根据索引查询一次就能找到对应的数据。如果是 B + 树索引,我们知道此时索引构造成了多个层级的树,当查询的索引在树的底层时,查询效率就越低。const 表示此时索引在第一层,只需访问一层便能得到数据。 - - `eq_ref`:使用唯一索引扫描,常见于多表连接中使用主键和唯一索引作为关联条件。 - - `ref`:非唯一索引扫描,还可见于唯一索引最左原则匹配扫描。 - - `range`:索引范围扫描,比如,<,>,between 等操作。 - - `index`:索引全表扫描,此时遍历整个索引树。 - - `ALL`:表示全表扫描,需要遍历全表来找到对应的行。 -- `possible_keys`:此次查询中可能选用的索引。 -- `key` ⭐:此次查询中实际使用的索引。 -- `ref`:哪个字段或常数与 key 一起被使用。 -- `rows` ⭐:显示此查询一共扫描了多少行,这个是一个估计值。 -- `filtered`:表示此查询条件所过滤的数据的百分比。 -- `extra`:额外的信息。 +良好的逻辑设计和物理设计是高性能的基石。 -> 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) +### 数据类型优化 -## optimizer trace +#### 数据类型优化基本原则 -在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。 +- **更小的通常更好** - 越小的数据类型通常会更快,占用更少的磁盘、内存,处理时需要的 CPU 周期也更少。 + - 例如:整型比字符类型操作代价低,因而会使用整型来存储 IP 地址,使用 `DATETIME` 来存储时间,而不是使用字符串。 +- **简单就好** - 如整型比字符型操作代价低。 + - 例如:很多软件会用整型来存储 IP 地址。 + - 例如:**`UNSIGNED` 表示不允许负值,大致可以使正数的上限提高一倍**。 +- **尽量避免 NULL** - 可为 NULL 的列会使得索引、索引统计和值比较都更复杂。 -如下代码所示,打开 optimizer_trace 后,再执行 SQL 就可以查询 information_schema.OPTIMIZER_TRACE 表查看执行计划了,最后可以关闭 optimizer_trace 功能: +#### 类型的选择 -```sql -SET optimizer_trace="enabled=on"; -SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-01-24 05:00 -SELECT * FROM information_schema.OPTIMIZER_TRACE; -SET optimizer_trace="enabled=off"; -``` +- 整数类型通常是标识列最好的选择,因为它们很快并且可以使用 `AUTO_INCREMENT`。 + +- `ENUM` 和 `SET` 类型通常是一个糟糕的选择,应尽量避免。 +- 应该尽量避免用字符串类型作为标识列,因为它们很消耗空间,并且通常比数字类型慢。对于 `MD5`、`SHA`、`UUID` 这类随机字符串,由于比较随机,所以可能分布在很大的空间内,导致 `INSERT` 以及一些 `SELECT` 语句变得很慢。 + - 如果存储 UUID ,应该移除 `-` 符号;更好的做法是,用 `UNHEX()` 函数转换 UUID 值为 16 字节的数字,并存储在一个 `BINARY(16)` 的列中,检索时,可以通过 `HEX()` 函数来格式化为 16 进制格式。 + +### 表设计 + +应该避免的设计问题: + +- **太多的列** - 设计者为了图方便,将大量冗余列加入表中,实际查询中,表中很多列是用不到的。这种宽表模式设计,会造成不小的性能代价,尤其是 `ALTER TABLE` 非常耗时。 +- **太多的关联** - 所谓的实体 - 属性 - 值(EAV)设计模式是一个常见的糟糕设计模式。Mysql 限制了每个关联操作最多只能有 61 张表,但 EAV 模式需要许多自关联。 +- **枚举** - 尽量不要用枚举,因为添加和删除字符串(枚举选项)必须使用 `ALTER TABLE`。 +- 尽量避免 `NULL` + +### 范式和反范式 + +**范式化目标是尽量减少冗余,而反范式化则相反**。 + +范式化的优点: + +- 比反范式更节省空间 +- 更新操作比反范式快 +- 更少需要 `DISTINCT` 或 `GROUP BY` 语句 + +范式化的缺点: + +- 通常需要关联查询。而关联查询代价较高,如果是分表的关联查询,代价更是高昂。 + +在真实世界中,很少会极端地使用范式化或反范式化。实际上,应该权衡范式和反范式的利弊,混合使用。 + +### 索引优化 + +> 索引优化应该是查询性能优化的最有效手段。 +> +> 如果想详细了解索引特性请参考:[Mysql 索引](https://github.com/dunwu/db-tutorial/blob/master/docs/sql/mysql/mysql-index.md) + +#### 何时使用索引 + +- 对于非常小的表,大部分情况下简单的全表扫描更高效。 +- 对于中、大型表,索引非常有效。 +- 对于特大型表,建立和使用索引的代价将随之增长。可以考虑使用分区技术。 +- 如果表的数量特别多,可以建立一个元数据信息表,用来查询需要用到的某些特性。 + +#### 索引优化策略 + +- **索引基本原则** + - 索引不是越多越好,不要为所有列都创建索引。 + - 要尽量避免冗余和重复索引。 + - 要考虑删除未使用的索引。 + - 尽量的扩展索引,不要新建索引。 + - 频繁作为 `WHERE` 过滤条件的列应该考虑添加索引。 +- **独立的列** - “独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数。 +- **前缀索引** - 索引很长的字符列,可以索引开始的部分字符,这样可以大大节约索引空间。 +- **最左匹配原则** - 将选择性高的列或基数大的列优先排在多列索引最前列。 +- **使用索引来排序** - 索引最好既满足排序,又用于查找行。这样,就可以使用索引来对结果排序。 +- `=`、`IN` 可以乱序 - 不需要考虑 `=`、`IN` 等的顺序 +- **覆盖索引** +- **自增字段作主键** ## 数据模型和业务 @@ -327,7 +399,9 @@ SET optimizer_trace="enabled=off"; ## 参考资料 - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) - [《Java 性能调优实战》](https://time.geekbang.org/column/intro/100028001) - [我必须得告诉大家的 MySQL 优化原理](https://www.jianshu.com/p/d7665192aaaf) - [20+ 条 MySQL 性能优化的最佳经验](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) -- [MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) \ No newline at end of file +- [Mysql 官方文档之执行计划](https://dev.mysql.com/doc/refman/8.0/en/execution-plan-information.html) +- [MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" index ab2b6480b3..f90e203bd6 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/20.Mysql\350\277\220\347\273\264.md" @@ -1,6 +1,8 @@ --- +icon: logos:mysql title: Mysql 运维 date: 2019-11-26 21:37:17 +order: 20 categories: - 数据库 - 关系型数据库 @@ -232,7 +234,7 @@ mysql> 连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 `show processlist` 命令中看到它。客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 `wait_timeout` 控制的,默认值是 8 小时。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200714115031.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200714115031.png) ### 创建用户 @@ -659,15 +661,15 @@ set global long_query_time = 3; ```sql mysql> show variables like 'transaction_isolation'; - + +-----------------------+----------------+ - + | Variable_name | Value | - + +-----------------------+----------------+ - + | transaction_isolation | READ-COMMITTED | - + +-----------------------+----------------+ ``` @@ -705,11 +707,10 @@ Default options are read from the following files in the given order: /usr/sbin/mysqld --auto_increment_offset=5 ``` -### 常用配置项说明 +### 基本配置模板 -> 这里介绍比较常用的基本配置,更多配置项说明可以参考:[Mysql 服务器配置说明](21.Mysql配置.md) +一个基本的 Mysql 配置模板大概如下: -先给出一份常用配置模板,内容如下: ```ini [mysqld] @@ -721,7 +722,7 @@ pid_file = /var/lib/mysql/mysql.pid user = mysql port = 3306 default_storage_engine = InnoDB -default_time_zone = '+8:00' +default_time_zone = '+8:00' character_set_server = utf8mb4 collation_server = utf8mb4_0900_ai_ci @@ -746,12 +747,409 @@ key_buffer_size = # ------------------------------------------------------------------------------- tmp_table_size = 32M max_heap_table_size = 32M -query_cache_type = 0 -query_cache_size = 0 max_connections = -thread_cache = open_files_limit = 65535 +[client] +socket = /var/lib/mysql/mysql.sock +port = 3306 +``` + +### 配置项说明 + +下面是一个较为详尽的 Mysql 配置文件,各配置项有注释说明: + +```ini +[mysqld] +# GENERAL +# ------------------------------------------------------------------------------- +datadir = /var/lib/mysql +# socket 文件 +socket = /var/lib/mysql/mysql.sock +# PID 文件 +pid_file = /var/lib/mysql/mysql.pid +# 启动 mysql 服务进程的用户 +user = mysql +# 服务端口号,默认 3306 +port = 3306 +default_storage_engine = InnoDB +# 默认时区 +default_time_zone = '+8:00' +character_set_server = utf8mb4 +collation_server = utf8mb4_0900_ai_ci + +# Mysql 服务 ID,单点服务时没必要设置 +server-id = 1 + +# 事务隔离级别,默认为可重复读(REPEATABLE-READ)。(此级别下可能参数很多间隙锁,影响性能,但是修改又影响主从复制及灾难恢复,建议还是修改代码逻辑吧) +# 隔离级别可选项目:READ-UNCOMMITTED READ-COMMITTED REPEATABLE-READ SERIALIZABLE +transaction_isolation = REPEATABLE-READ + +# 目录配置 +# ------------------------------------------------------------------------------- + +# mysql 安装根目录 +basedir = /usr/local/mysql-5.7.21 + +# mysql 数据文件所在目录 +datadir = /var/lib/mysql + +# 临时目录 比如 load data infile 会用到,一般都是使用/tmp +tmpdir = /tmp + +# 数据库引擎配置 +# ------------------------------------------------------------------------------- + +# mysql 5.1 之后,默认引擎是 InnoDB +default_storage_engine = InnoDB + +# 内存临时表默认引擎,默认 InnoDB +default_tmp_storage_engine = InnoDB + +# mysql 5.7 新增特性,磁盘临时表默认引擎,默认 InnoDB +internal_tmp_disk_storage_engine = InnoDB + +# 字符集配置 +# ------------------------------------------------------------------------------- + +# 数据库默认字符集,主流字符集支持一些特殊表情符号(特殊表情符占用 4 个字节) +character_set_server = utf8mb4 + +# 数据库字符集对应一些排序等规则,注意要和 character_set_server 对应 +collation-server = utf8mb4_0900_ai_ci + +# 设置 client 连接 mysql 时的字符集,防止乱码 +# init_connect='SET NAMES utf8' + +# 是否对 sql 语句大小写敏感,默认值为 0,1 表示不敏感 +lower_case_table_names = 1 + +# 数据库连接配置 +# ------------------------------------------------------------------------------- + +# 最大连接数,可设最大值 16384,一般考虑根据同时在线人数设置一个比较综合的数字,鉴于该数值增大并不太消耗系统资源,建议直接设 10000 +# 如果在访问时经常出现 Too Many Connections 的错误提示,则需要增大该参数值 +max_connections = 10000 + +# 默认值 100,最大错误连接数,如果有超出该参数值个数的中断错误连接,则该主机将被禁止连接。如需对该主机进行解禁,执行:FLUSH HOST +# 考虑高并发场景下的容错,建议加大。 +max_connect_errors = 10000 + +# MySQL 打开的文件描述符限制,默认最小 1024; +# 当 open_files_limit 没有被配置的时候,比较 max_connections\*5 和 ulimit -n 的值,哪个大用哪个, +# 当 open_file_limit 被配置的时候,比较 open_files_limit 和 max_connections\*5 的值,哪个大用哪个。 +# 注意:仍然可能出现报错信息 Can't create a new thread;此时观察系统 cat /proc/mysql 进程号/limits,观察进程 ulimit 限制情况 +# 过小的话,考虑修改系统配置表,/etc/security/limits.conf 和 /etc/security/limits.d/90-nproc.conf +open_files_limit = 65535 + +# 超时配置 +# ------------------------------------------------------------------------------- + +# MySQL 默认的 wait_timeout 值为 8 个小时,interactive_timeout 参数需要同时配置才能生效 +# MySQL 连接闲置超过一定时间后(单位:秒,此处为 1800 秒)将会被强行关闭 +interactive_timeout = 1800 +wait_timeout = 1800 + +# 在 MySQL 暂时停止响应新请求之前的短时间内多少个请求可以被存在堆栈中 +# 官方建议 back_log = 50 + (max_connections / 5),封顶数为 900 +back_log = 900 + +# 数据库数据交换配置 +# ------------------------------------------------------------------------------- +# 该参数限制服务器端,接受的数据包大小,如果有 BLOB 子段,建议增大此值,避免写入或者更新出错。有 BLOB 子段,建议改为 1024M +max_allowed_packet = 128M + +# 内存、cache 与 buffer 设置 + +# 内存临时表的最大值,默认 16M,此处设置成 64M +tmp_table_size = 64M + +# 用户创建的内存表的大小,默认 16M,往往和 tmp_table_size 一起设置,限制用户临时表大小。 +# 超限的话,MySQL 就会自动地把它转化为基于磁盘的 MyISAM 表,存储在指定的 tmpdir 目录下,增大 IO 压力,建议内存大,增大该数值。 +max_heap_table_size = 64M + +# 表示这个 mysql 版本是否支持查询缓存。ps:SHOW STATUS LIKE 'qcache%',与缓存相关的状态变量。 +# have_query_cache + +# 这个系统变量控制着查询缓存功能的开启和关闭,0 表示关闭,1 表示打开,2 表示只要 select 中明确指定 SQL_CACHE 才缓存。 +# 看业务场景决定是否使用缓存,不使用,下面就不用配置了。 +# Mysql8 不支持 +query_cache_type = 0 + +# 默认值 1M,优点是查询缓存可以极大的提高服务器速度,如果你有大量的相同的查询并且很少修改表。 +# 缺点:在你表经常变化的情况下或者如果你的查询原文每次都不同,查询缓存也许引起性能下降而不是性能提升。 +# Mysql8 不支持 +query_cache_size = 64M + +# 只有小于此设定值的结果才会被缓冲,保护查询缓冲,防止一个极大的结果集将其他所有的查询结果都覆盖。 +query_cache_limit = 2M + +# 每个被缓存的结果集要占用的最小内存,默认值 4kb,一般不怎么调整。 +# 如果 Qcache_free_blocks 值过大,可能是 query_cache_min_res_unit 值过大,应该调小些 +# query_cache_min_res_unit 的估计值:(query_cache_size - Qcache_free_memory) / Qcache_queries_in_cache +query_cache_min_res_unit = 4kb + +# 在一个事务中 binlog 为了记录 SQL 状态所持有的 cache 大小 +# 如果你经常使用大的、多声明的事务,你可以增加此值来获取更大的性能。 +# 所有从事务来的状态都将被缓冲在 binlog 缓冲中然后在提交后一次性写入到 binlog 中 +# 如果事务比此值大,会使用磁盘上的临时文件来替代。 +# 此缓冲在每个连接的事务第一次更新状态时被创建 +binlog_cache_size = 1M + +# 日志配置 +# ------------------------------------------------------------------------------- + +# 日志文件相关设置,一般只开启三种日志,错误日志,慢查询日志,二进制日志。普通查询日志不开启。 +# 普通查询日志,默认值 off,不开启 +general_log = 0 + +# 普通查询日志存放地址 +general_log_file = /usr/local/mysql-5.7.21/log/mysql-general.log + +# 全局动态变量,默认 3,范围:1 ~ 3 +# 表示错误日志记录的信息,1:只记录 error 信息;2:记录 error 和 warnings 信息;3:记录 error、warnings 和普通的 notes 信息。 +log_error_verbosity = 2 + +# 错误日志文件地址 +log_error = /usr/local/mysql-5.7.21/log/mysql-error.log + +# 开启慢查询 +slow_query_log = 1 + +# 开启慢查询时间,此处为 1 秒,达到此值才记录数据 +long_query_time = 3 + +# 检索行数达到此数值,才记录慢查询日志中 +min_examined_row_limit = 100 + +# mysql 5.6.5 新增,用来表示每分钟允许记录到 slow log 的且未使用索引的 SQL 语句次数,默认值为 0,不限制。 +log_throttle_queries_not_using_indexes = 0 + +# 慢查询日志文件地址 +slow_query_log_file = /var/log/mysql/mysql-slow.log + +# 开启记录没有使用索引查询语句 +log-queries-not-using-indexes = 1 + +# 开启二进制日志 +log_bin = /usr/local/mysql-5.7.21/log/mysql-bin.log + +# mysql 清除过期日志的时间,默认值 0,不自动清理,而是使用滚动循环的方式。 +expire_logs_days = 0 + +# 如果二进制日志写入的内容超出给定值,日志就会发生滚动。你不能将该变量设置为大于 1GB 或小于 4096 字节。 默认值是 1GB。 +max_binlog_size = 1000M + +# binlog 的格式也有三种:STATEMENT,ROW,MIXED。mysql 5.7.7 后,默认值从 MIXED 改为 ROW +# 关于 binlog 日志格式问题,请查阅网络资料 +binlog_format = row + +# 表示每 N 次写入 binlog 后,持久化到磁盘,默认值 N=1 +# 建议设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 +# sync_binlog = 1 + +# InnoDB 引擎配置 +# ------------------------------------------------------------------------------- + +# 说明:该参数可以提升扩展性和刷脏页性能。 +# 默认值 1,建议值:4-8;并且必须小于 innodb_buffer_pool_instances +innodb_page_cleaners = 4 + +# 说明:一般 8k 和 16k 中选择,8k 的话,cpu 消耗小些,selcet 效率高一点,一般不用改 +# 默认值:16k;建议值:不改, +innodb_page_size = 16384 + +# 说明:InnoDB 使用一个缓冲池来保存索引和原始数据,不像 MyISAM。这里你设置越大,你在存取表里面数据时所需要的磁盘 I/O 越少。 +# 在一个独立使用的数据库服务器上,你可以设置这个变量到服务器物理内存大小的 60%-80% +# 注意别设置的过大,会导致 system 的 swap 空间被占用,导致操作系统变慢,从而减低 sql 查询的效率 +# 默认值:128M,建议值:物理内存的 60%-80% +innodb_buffer_pool_size = 512M + +# 说明:只有当设置 innodb_buffer_pool_size 值大于 1G 时才有意义,小于 1G,instances 默认为 1,大于 1G,instances 默认为 8 +# 但是网络上有评价,最佳性能,每个实例至少 1G 大小。 +# 默认值:1 或 8,建议值:innodb_buffer_pool_size/innodb_buffer_pool_instances >= 1G +innodb_buffer_pool_instances = 1 + +# 说明:mysql 5.7 新特性,defines the chunk size for online InnoDB buffer pool resizing operations。 +# 实际缓冲区大小必须为 innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances*倍数,取略大于 innodb_buffer_pool_size +# 默认值 128M,建议值:默认值就好,乱改反而容易出问题,它会影响实际 buffer pool 大小。 +innodb_buffer_pool_chunk_size = 128M + +# 在启动时把热数据加载到内存。默认值为 on,不修改 +innodb_buffer_pool_load_at_startup = 1 + +# 在关闭时把热数据 dump 到本地磁盘。默认值为 on,不修改 +innodb_buffer_pool_dump_at_shutdown = 1 + +# 说明:影响 Innodb 缓冲区的刷新算法,建议从小到大配置,直到 zero free pages;innodb_lru_scan_depth \* innodb_buffer_pool_instances defines the amount of work performed by the page cleaner thread each second。 +# 默认值 1024,建议值: 未知 +innodb_lru_scan_depth = 1024 + +# 说明:事务等待获取资源等待的最长时间,单位为秒,看具体业务情况,一般默认值就好 +# 默认值:50,建议值:看业务。 +innodb_lock_wait_timeout = 60 + +# 说明:设置了 Mysql 后台任务(例如页刷新和 merge dadta from buffer pool)每秒 io 操作的上限。 +# 默认值:200,建议值:方法一,单盘 sata 设 100,sas10,raid10 设 200,ssd 设 2000,fushion-io 设 50000;方法二,通过测试工具获得磁盘 io 性能后,设置 IOPS 数值/2。 +innodb_io_capacity = 2000 + +# 说明:该参数是所有缓冲区线程 io 操作的总上限。 +# 默认值:innodb_io_capacity 的两倍。建议值:例如用 iometer 测试后的 iops 数值就好 +innodb_io_capacity_max = 4000 + +# 说明:控制着 innodb 数据文件及 redo log 的打开、刷写模式,三种模式:fdatasync(默认),O_DSYNC,O_DIRECT +# fdatasync:数据文件,buffer pool->os buffer->磁盘;日志文件,buffer pool->os buffer->磁盘; +# O_DSYNC: 数据文件,buffer pool->os buffer->磁盘;日志文件,buffer pool->磁盘; +# O_DIRECT: 数据文件,buffer pool->磁盘; 日志文件,buffer pool->os buffer->磁盘; +# 默认值为空,建议值:使用 SAN 或者 raid,建议用 O_DIRECT,不懂测试的话,默认生产上使用 O_DIRECT +innodb_flush_method = O_DIRECT + +# 说明:mysql5.7 之后默认开启,意思是,每张表一个独立表空间。 +# 默认值 1,开启 +innodb_file_per_table = 1 + +# 说明:The path where InnoDB creates undo tablespaces。通常等于 undo log 文件的存放目录。 +# 默认值 ./;自行设置 +innodb_undo_directory = /usr/local/mysql-5.7.21/log + +# 说明:The number of undo tablespaces used by InnoDB 等于 undo log 文件数量。5.7.21 后开始弃用 +# 默认值为 0,建议默认值就好,不用调整了。 +innodb_undo_tablespaces = 0 + +# 说明:定义 undo 使用的回滚段数量。5.7.19 后弃用 +# 默认值 128,建议不动,以后弃用了。 +innodb_undo_logs = 128 + +# 说明:5.7.5 后开始使用,在线收缩 undo log 使用的空间。 +# 默认值:关闭,建议值:开启 +innodb_undo_log_truncate = 1 + +# 说明:结合 innodb_undo_log_truncate,实现 undo 空间收缩功能 +# 默认值:1G,建议值,不改。 +innodb_max_undo_log_size = 1G + +# 说明:重作日志文件的存放目录 +innodb_log_group_home_dir = /usr/local/mysql-5.7.21/log + +# 说明:日志文件的大小 +# 默认值:48M,建议值:根据你系统的磁盘空间和日志增长情况调整大小 +innodb_log_file_size = 128M + +# 说明:日志组中的文件数量,mysql 以循环方式写入日志 +# 默认值 2,建议值:根据你系统的磁盘空间和日志增长情况调整大小 +innodb_log_files_in_group = 3 + +# 此参数确定些日志文件所用的内存大小,以 M 为单位。缓冲区更大能提高性能,但意外的故障将会丢失数据。MySQL 开发人员建议设置为 1-8M 之间 +innodb_log_buffer_size = 16M + +# 说明:可以控制 log 从系统 buffer 刷入磁盘文件的刷新频率,增大可减轻系统负荷 +# 默认值是 1;建议值不改。系统性能一般够用。 +innodb_flush_log_at_timeout = 1 + +# 说明:参数可设为 0,1,2; +# 参数 0:表示每秒将 log buffer 内容刷新到系统 buffer 中,再调用系统 flush 操作写入磁盘文件。 +# 参数 1:表示每次事务提交,redo log 都直接持久化到磁盘。 +# 参数 2:表示每次事务提交,隔 1 秒后再将 redo log 持久化到磁盘。 +# 建议设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。 +innodb_flush_log_at_trx_commit = 1 + +# 说明:限制 Innodb 能打开的表的数据,如果库里的表特别多的情况,请增加这个。 +# 值默认是 2000,建议值:参考数据库表总数再进行调整,一般够用不用调整。 +innodb_open_files = 8192 + +# innodb 处理 io 读写的后台并发线程数量,根据 cpu 核来确认,取值范围:1-64 +# 默认值:4,建议值:与逻辑 cpu 数量的一半保持一致。 +innodb_read_io_threads = 4 +innodb_write_io_threads = 4 + +# 默认设置为 0,表示不限制并发数,这里推荐设置为 0,更好去发挥 CPU 多核处理能力,提高并发量 +innodb_thread_concurrency = 0 + +# 默认值为 4,建议不变。InnoDB 中的清除操作是一类定期回收无用数据的操作。mysql 5.5 之后,支持多线程清除操作。 +innodb_purge_threads = 4 + +# 说明:mysql 缓冲区分为 new blocks 和 old blocks;此参数表示 old blocks 占比; +# 默认值:37,建议值,一般不动 +innodb_old_blocks_pct = 37 + +# 说明:新数据被载入缓冲池,进入 old pages 链区,当 1 秒后再次访问,则提升进入 new pages 链区。 +# 默认值:1000 +innodb_old_blocks_time=1000 + +# 说明:开启异步 io,可以提高并发性,默认开启。 +# 默认值为 1,建议不动 +innodb_use_native_aio = 1 + +# 说明:默认为空,使用 data 目录,一般不改。 +innodb_data_home_dir=/usr/local/mysql-5.7.21/data + +# 说明:Defines the name,size,and attributes of InnoDB system tablespace data files。 +# 默认值,不指定,默认为 ibdata1:12M:autoextend +innodb_data_file_path = ibdata1:12M:autoextend + +# 说明:设置了 InnoDB 存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,除非你的数据对象及其多,否则一般默认不改。 +# innodb_additional_mem_pool_size = 16M +# 说明:The crash recovery mode。只有紧急情况需要恢复数据的时候,才改为大于 1-6 之间数值,含义查下官网。 +# 默认值为 0; +#innodb_force_recovery = 0 + +# MyISAM 引擎配置 +# ------------------------------------------------------------------------------- + +# 指定索引缓冲区的大小,为 MYISAM 数据表开启供线程共享的索引缓存,对 INNODB 引擎无效。相当影响 MyISAM 的性能。 +# 不要将其设置大于你可用内存的 30%,因为一部分内存同样被 OS 用来缓冲行数据 +# 甚至在你并不使用 MyISAM 表的情况下,你也需要仍旧设置起 8-64M 内存由于它同样会被内部临时磁盘表使用。 +# 默认值 8M,建议值:对于内存在 4GB 左右的服务器该参数可设置为 256M 或 384M。注意:该参数值设置的过大反而会是服务器整体效率降低! +key_buffer_size = 64M + +# 为每个扫描 MyISAM 的线程分配参数设置的内存大小缓冲区。 +# 默认值 128kb,建议值:16G 内存建议 1M,4G:128kb 或者 256kb 吧 +# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 128kb*连接数;极端情况 128kb*maxconnectiosns,会超级大,所以要考虑日常平均连接数。 +# 一般不需要太关心该数值,稍微增大就可以了, +read_buffer_size = 262144 + +# 支持任何存储引擎 +# MySQL 的随机读缓冲区大小,适当增大,可以提高性能。 +# 默认值 256kb;建议值:得参考连接数,16G 内存,有人推荐 8M +# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 128kb*连接数;极端情况 128kb*maxconnectiosns,会超级大,所以要考虑日常平均连接数。 +read_rnd_buffer_size = 1M + +# order by 或 group by 时用到 +# 支持所有引擎,innodb 和 myisam 有自己的 innodb_sort_buffer_size 和 myisam_sort_buffer_size 设置 +# 默认值 256kb;建议值:得参考连接数,16G 内存,有人推荐 8M。 +# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 1M*连接数;极端情况 1M*maxconnectiosns,会超级大。所以要考虑日常平均连接数。 +sort_buffer_size = 1M + +# 此缓冲被使用来优化全联合(full JOINs 不带索引的联合) +# 类似的联合在极大多数情况下有非常糟糕的性能表现,但是将此值设大能够减轻性能影响。 +# 通过 “Select_full_join” 状态变量查看全联合的数量 +# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 1M*连接数;极端情况 1M*maxconnectiosns,会超级大。所以要考虑日常平均连接数。 +# 默认值 256kb;建议值:16G 内存,设置 8M。 +join_buffer_size = 1M + +# 缓存 linux 文件描述符信息,加快数据文件打开速度 +# 它影响 myisam 表的打开关闭,但是不影响 innodb 表的打开关闭。 +# 默认值 2000,建议值:根据状态变量 Opened_tables 去设定 +table_open_cache = 2000 + +# 缓存表定义的相关信息,加快读取表信息速度 +# 默认值 1400,最大值 2000,建议值:基本不改。 +table_definition_cache = 1400 + +# 该参数是 myssql 5.6 后引入的,目的是提高并发。 +# 默认值 1,建议值:cpu 核数,并且<=16 +table_open_cache_instances = 2 + +# 当客户端断开之后,服务器处理此客户的线程将会缓存起来以响应下一个客户而不是销毁。可重用,减小了系统开销。 +# 默认值为 9,建议值:两种取值方式,方式一,根据物理内存,1G —> 8;2G —> 16; 3G —> 32; >3G —> 64; +# 方式二,根据 show status like 'threads%',查看 Threads_connected 值。 +thread_cache_size = 16 + +# 默认值 256k,建议值:16/32G 内存,512kb,其他一般不改变,如果报错:Thread stack overrun,就增大看看, +# 注意,每个线程分配内存空间,所以总内存空间。。。你懂得。 +thread_stack = 512k + + [client] socket = /var/lib/mysql/mysql.sock port = 3306 @@ -953,4 +1351,5 @@ Query OK, 0 rows affected (0.00 sec) - https://www.cnblogs.com/xyabk/p/8967990.html - [MySQL 8.0 主从(Master-Slave)配置](https://blog.csdn.net/zyhlwzy/article/details/80569422) - [Mysql 主从同步实战](https://juejin.im/post/58eb5d162f301e00624f014a) -- [MySQL 备份和恢复机制](https://juejin.im/entry/5a0aa2026fb9a045132a369f) \ No newline at end of file +- [MySQL 备份和恢复机制](https://juejin.im/entry/5a0aa2026fb9a045132a369f) +- [Mysql 配置文件/etc/my.cnf 解析](https://www.jianshu.com/p/5f39c486561b) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" deleted file mode 100644 index d558d801e5..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/21.Mysql\351\205\215\347\275\256.md" +++ /dev/null @@ -1,492 +0,0 @@ ---- -title: Mysql 配置 -date: 2020-02-29 22:32:57 -categories: - - 数据库 - - 关系型数据库 - - Mysql -tags: - - 数据库 - - 关系型数据库 - - Mysql - - 配置 -permalink: /pages/5da42d/ ---- - -# Mysql 配置 - -> 版本:![mysql](https://img.shields.io/badge/mysql-8.0-blue) - -## 基本配置 - -```ini -[mysqld] -# GENERAL -# ------------------------------------------------------------------------------- -datadir = /var/lib/mysql -socket = /var/lib/mysql/mysql.sock -pid_file = /var/lib/mysql/mysql.pid -user = mysql -port = 3306 -default_storage_engine = InnoDB -default_time_zone = '+8:00' -character_set_server = utf8mb4 -collation_server = utf8mb4_0900_ai_ci - -# LOG -# ------------------------------------------------------------------------------- -log_error = /var/log/mysql/mysql-error.log -slow_query_log = 1 -slow_query_log_file = /var/log/mysql/mysql-slow.log - -# InnoDB -# ------------------------------------------------------------------------------- -innodb_buffer_pool_size = -innodb_log_file_size = -innodb_file_per_table = 1 -innodb_flush_method = O_DIRECT - -# MyIsam -# ------------------------------------------------------------------------------- -key_buffer_size = - -# OTHER -# ------------------------------------------------------------------------------- -tmp_table_size = 32M -max_heap_table_size = 32M -max_connections = -open_files_limit = 65535 - -[client] -socket = /var/lib/mysql/mysql.sock -port = 3306 -``` - -## 配置项说明 - -```ini -[client] -# 服务端口号,默认 3306 -port = 3306 - -# socket 文件 -socket = /var/lib/mysql/mysql.sock - - - -[mysqld] - -# GENERAL -# ------------------------------------------------------------------------------- - -# socket 文件 -socket = /var/lib/mysql/mysql.sock - -# PID 文件 -pid_file = /var/lib/mysql/mysql.pid - -# 启动 mysql 服务进程的用户 -user = mysql - -# 服务端口号,默认 3306 -port = 3306 - -# 默认时区 -default_time_zone = '+8:00' - -# Mysql 服务 ID,单点服务时没必要设置 -server-id = 1 - -# 事务隔离级别,默认为可重复读(REPEATABLE-READ)。(此级别下可能参数很多间隙锁,影响性能,但是修改又影响主从复制及灾难恢复,建议还是修改代码逻辑吧) -# 隔离级别可选项目:READ-UNCOMMITTED READ-COMMITTED REPEATABLE-READ SERIALIZABLE -transaction_isolation = REPEATABLE-READ - -# 目录配置 -# ------------------------------------------------------------------------------- - -# mysql 安装根目录 -basedir = /usr/local/mysql-5.7.21 - -# mysql 数据文件所在目录 -datadir = /var/lib/mysql - -# 临时目录 比如 load data infile 会用到,一般都是使用/tmp -tmpdir = /tmp - -# 数据库引擎配置 -# ------------------------------------------------------------------------------- - -# mysql 5.1 之后,默认引擎是 InnoDB -default_storage_engine = InnoDB - -# 内存临时表默认引擎,默认 InnoDB -default_tmp_storage_engine = InnoDB - -# mysql 5.7 新增特性,磁盘临时表默认引擎,默认 InnoDB -internal_tmp_disk_storage_engine = InnoDB - -# 字符集配置 -# ------------------------------------------------------------------------------- - -# 数据库默认字符集,主流字符集支持一些特殊表情符号(特殊表情符占用 4 个字节) -character_set_server = utf8mb4 - -# 数据库字符集对应一些排序等规则,注意要和 character_set_server 对应 -collation-server = utf8mb4_0900_ai_ci - -# 设置 client 连接 mysql 时的字符集,防止乱码 -# init_connect='SET NAMES utf8' - -# 是否对 sql 语句大小写敏感,默认值为 0,1 表示不敏感 -lower_case_table_names = 1 - -# 数据库连接配置 -# ------------------------------------------------------------------------------- - -# 最大连接数,可设最大值 16384,一般考虑根据同时在线人数设置一个比较综合的数字,鉴于该数值增大并不太消耗系统资源,建议直接设 10000 -# 如果在访问时经常出现 Too Many Connections 的错误提示,则需要增大该参数值 -max_connections = 10000 - -# 默认值 100,最大错误连接数,如果有超出该参数值个数的中断错误连接,则该主机将被禁止连接。如需对该主机进行解禁,执行:FLUSH HOST -# 考虑高并发场景下的容错,建议加大。 -max_connect_errors = 10000 - -# MySQL 打开的文件描述符限制,默认最小 1024; -# 当 open_files_limit 没有被配置的时候,比较 max_connections\*5 和 ulimit -n 的值,哪个大用哪个, -# 当 open_file_limit 被配置的时候,比较 open_files_limit 和 max_connections\*5 的值,哪个大用哪个。 -# 注意:仍然可能出现报错信息 Can't create a new thread;此时观察系统 cat /proc/mysql 进程号/limits,观察进程 ulimit 限制情况 -# 过小的话,考虑修改系统配置表,/etc/security/limits.conf 和 /etc/security/limits.d/90-nproc.conf -open_files_limit = 65535 - -# 超时配置 -# ------------------------------------------------------------------------------- - -# MySQL 默认的 wait_timeout 值为 8 个小时,interactive_timeout 参数需要同时配置才能生效 -# MySQL 连接闲置超过一定时间后(单位:秒,此处为 1800 秒)将会被强行关闭 -interactive_timeout = 1800 -wait_timeout = 1800 - -# 在 MySQL 暂时停止响应新请求之前的短时间内多少个请求可以被存在堆栈中 -# 官方建议 back_log = 50 + (max_connections / 5),封顶数为 900 -back_log = 900 - -# 数据库数据交换配置 -# ------------------------------------------------------------------------------- -# 该参数限制服务器端,接受的数据包大小,如果有 BLOB 子段,建议增大此值,避免写入或者更新出错。有 BLOB 子段,建议改为 1024M -max_allowed_packet = 128M - -# 内存、cache 与 buffer 设置 - -# 内存临时表的最大值,默认 16M,此处设置成 64M -tmp_table_size = 64M - -# 用户创建的内存表的大小,默认 16M,往往和 tmp_table_size 一起设置,限制用户临时表大小。 -# 超限的话,MySQL 就会自动地把它转化为基于磁盘的 MyISAM 表,存储在指定的 tmpdir 目录下,增大 IO 压力,建议内存大,增大该数值。 -max_heap_table_size = 64M - -# 表示这个 mysql 版本是否支持查询缓存。ps:SHOW STATUS LIKE 'qcache%',与缓存相关的状态变量。 -# have_query_cache - -# 这个系统变量控制着查询缓存功能的开启和关闭,0 表示关闭,1 表示打开,2 表示只要 select 中明确指定 SQL_CACHE 才缓存。 -# 看业务场景决定是否使用缓存,不使用,下面就不用配置了。 -# Mysql8 不支持 -query_cache_type = 0 - -# 默认值 1M,优点是查询缓存可以极大的提高服务器速度,如果你有大量的相同的查询并且很少修改表。 -# 缺点:在你表经常变化的情况下或者如果你的查询原文每次都不同,查询缓存也许引起性能下降而不是性能提升。 -# Mysql8 不支持 -query_cache_size = 64M - -# 只有小于此设定值的结果才会被缓冲,保护查询缓冲,防止一个极大的结果集将其他所有的查询结果都覆盖。 -query_cache_limit = 2M - -# 每个被缓存的结果集要占用的最小内存,默认值 4kb,一般不怎么调整。 -# 如果 Qcache_free_blocks 值过大,可能是 query_cache_min_res_unit 值过大,应该调小些 -# query_cache_min_res_unit 的估计值:(query_cache_size - Qcache_free_memory) / Qcache_queries_in_cache -query_cache_min_res_unit = 4kb - -# 在一个事务中 binlog 为了记录 SQL 状态所持有的 cache 大小 -# 如果你经常使用大的、多声明的事务,你可以增加此值来获取更大的性能。 -# 所有从事务来的状态都将被缓冲在 binlog 缓冲中然后在提交后一次性写入到 binlog 中 -# 如果事务比此值大,会使用磁盘上的临时文件来替代。 -# 此缓冲在每个连接的事务第一次更新状态时被创建 -binlog_cache_size = 1M - -# 日志配置 -# ------------------------------------------------------------------------------- - -# 日志文件相关设置,一般只开启三种日志,错误日志,慢查询日志,二进制日志。普通查询日志不开启。 -# 普通查询日志,默认值 off,不开启 -general_log = 0 - -# 普通查询日志存放地址 -general_log_file = /usr/local/mysql-5.7.21/log/mysql-general.log - -# 全局动态变量,默认 3,范围:1 ~ 3 -# 表示错误日志记录的信息,1:只记录 error 信息;2:记录 error 和 warnings 信息;3:记录 error、warnings 和普通的 notes 信息。 -log_error_verbosity = 2 - -# 错误日志文件地址 -log_error = /usr/local/mysql-5.7.21/log/mysql-error.log - -# 开启慢查询 -slow_query_log = 1 - -# 开启慢查询时间,此处为 1 秒,达到此值才记录数据 -long_query_time = 3 - -# 检索行数达到此数值,才记录慢查询日志中 -min_examined_row_limit = 100 - -# mysql 5.6.5 新增,用来表示每分钟允许记录到 slow log 的且未使用索引的 SQL 语句次数,默认值为 0,不限制。 -log_throttle_queries_not_using_indexes = 0 - -# 慢查询日志文件地址 -slow_query_log_file = /var/log/mysql/mysql-slow.log - -# 开启记录没有使用索引查询语句 -log-queries-not-using-indexes = 1 - -# 开启二进制日志 -log_bin = /usr/local/mysql-5.7.21/log/mysql-bin.log - -# mysql 清除过期日志的时间,默认值 0,不自动清理,而是使用滚动循环的方式。 -expire_logs_days = 0 - -# 如果二进制日志写入的内容超出给定值,日志就会发生滚动。你不能将该变量设置为大于 1GB 或小于 4096 字节。 默认值是 1GB。 -max_binlog_size = 1000M - -# binlog 的格式也有三种:STATEMENT,ROW,MIXED。mysql 5.7.7 后,默认值从 MIXED 改为 ROW -# 关于 binlog 日志格式问题,请查阅网络资料 -binlog_format = row - -# 表示每 N 次写入 binlog 后,持久化到磁盘,默认值 N=1 -# 建议设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 -# sync_binlog = 1 - -# MyISAM 引擎配置 -# ------------------------------------------------------------------------------- - -# 指定索引缓冲区的大小,为 MYISAM 数据表开启供线程共享的索引缓存,对 INNODB 引擎无效。相当影响 MyISAM 的性能。 -# 不要将其设置大于你可用内存的 30%,因为一部分内存同样被 OS 用来缓冲行数据 -# 甚至在你并不使用 MyISAM 表的情况下,你也需要仍旧设置起 8-64M 内存由于它同样会被内部临时磁盘表使用。 -# 默认值 8M,建议值:对于内存在 4GB 左右的服务器该参数可设置为 256M 或 384M。注意:该参数值设置的过大反而会是服务器整体效率降低! -key_buffer_size = 64M - -# 为每个扫描 MyISAM 的线程分配参数设置的内存大小缓冲区。 -# 默认值 128kb,建议值:16G 内存建议 1M,4G:128kb 或者 256kb 吧 -# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 128kb*连接数;极端情况 128kb*maxconnectiosns,会超级大,所以要考虑日常平均连接数。 -# 一般不需要太关心该数值,稍微增大就可以了, -read_buffer_size = 262144 - -# 支持任何存储引擎 -# MySQL 的随机读缓冲区大小,适当增大,可以提高性能。 -# 默认值 256kb;建议值:得参考连接数,16G 内存,有人推荐 8M -# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 128kb*连接数;极端情况 128kb*maxconnectiosns,会超级大,所以要考虑日常平均连接数。 -read_rnd_buffer_size = 1M - -# order by 或 group by 时用到 -# 支持所有引擎,innodb 和 myisam 有自己的 innodb_sort_buffer_size 和 myisam_sort_buffer_size 设置 -# 默认值 256kb;建议值:得参考连接数,16G 内存,有人推荐 8M。 -# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 1M*连接数;极端情况 1M*maxconnectiosns,会超级大。所以要考虑日常平均连接数。 -sort_buffer_size = 1M - -# 此缓冲被使用来优化全联合(full JOINs 不带索引的联合) -# 类似的联合在极大多数情况下有非常糟糕的性能表现,但是将此值设大能够减轻性能影响。 -# 通过 “Select_full_join” 状态变量查看全联合的数量 -# 注意,该缓冲区是每个连接独占的,所以总缓冲区大小为 1M*连接数;极端情况 1M*maxconnectiosns,会超级大。所以要考虑日常平均连接数。 -# 默认值 256kb;建议值:16G 内存,设置 8M。 -join_buffer_size = 1M - -# 缓存 linux 文件描述符信息,加快数据文件打开速度 -# 它影响 myisam 表的打开关闭,但是不影响 innodb 表的打开关闭。 -# 默认值 2000,建议值:根据状态变量 Opened_tables 去设定 -table_open_cache = 2000 - -# 缓存表定义的相关信息,加快读取表信息速度 -# 默认值 1400,最大值 2000,建议值:基本不改。 -table_definition_cache = 1400 - -# 该参数是 myssql 5.6 后引入的,目的是提高并发。 -# 默认值 1,建议值:cpu 核数,并且<=16 -table_open_cache_instances = 2 - -# 当客户端断开之后,服务器处理此客户的线程将会缓存起来以响应下一个客户而不是销毁。可重用,减小了系统开销。 -# 默认值为 9,建议值:两种取值方式,方式一,根据物理内存,1G —> 8;2G —> 16; 3G —> 32; >3G —> 64; -# 方式二,根据 show status like 'threads%',查看 Threads_connected 值。 -thread_cache_size = 16 - -# 默认值 256k,建议值:16/32G 内存,512kb,其他一般不改变,如果报错:Thread stack overrun,就增大看看, -# 注意,每个线程分配内存空间,所以总内存空间。。。你懂得。 -thread_stack = 512k - -# InnoDB 引擎配置 -# ------------------------------------------------------------------------------- - -# 说明:该参数可以提升扩展性和刷脏页性能。 -# 默认值 1,建议值:4-8;并且必须小于 innodb_buffer_pool_instances -innodb_page_cleaners = 4 - -# 说明:一般 8k 和 16k 中选择,8k 的话,cpu 消耗小些,selcet 效率高一点,一般不用改 -# 默认值:16k;建议值:不改, -innodb_page_size = 16384 - -# 说明:InnoDB 使用一个缓冲池来保存索引和原始数据,不像 MyISAM。这里你设置越大,你在存取表里面数据时所需要的磁盘 I/O 越少。 -# 在一个独立使用的数据库服务器上,你可以设置这个变量到服务器物理内存大小的 60%-80% -# 注意别设置的过大,会导致 system 的 swap 空间被占用,导致操作系统变慢,从而减低 sql 查询的效率 -# 默认值:128M,建议值:物理内存的 60%-80% -innodb_buffer_pool_size = 512M - -# 说明:只有当设置 innodb_buffer_pool_size 值大于 1G 时才有意义,小于 1G,instances 默认为 1,大于 1G,instances 默认为 8 -# 但是网络上有评价,最佳性能,每个实例至少 1G 大小。 -# 默认值:1 或 8,建议值:innodb_buffer_pool_size/innodb_buffer_pool_instances >= 1G -innodb_buffer_pool_instances = 1 - -# 说明:mysql 5.7 新特性,defines the chunk size for online InnoDB buffer pool resizing operations。 -# 实际缓冲区大小必须为 innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances*倍数,取略大于 innodb_buffer_pool_size -# 默认值 128M,建议值:默认值就好,乱改反而容易出问题,它会影响实际 buffer pool 大小。 -innodb_buffer_pool_chunk_size = 128M - -# 在启动时把热数据加载到内存。默认值为 on,不修改 -innodb_buffer_pool_load_at_startup = 1 - -# 在关闭时把热数据 dump 到本地磁盘。默认值为 on,不修改 -innodb_buffer_pool_dump_at_shutdown = 1 - -# 说明:影响 Innodb 缓冲区的刷新算法,建议从小到大配置,直到 zero free pages;innodb_lru_scan_depth \* innodb_buffer_pool_instances defines the amount of work performed by the page cleaner thread each second。 -# 默认值 1024,建议值: 未知 -innodb_lru_scan_depth = 1024 - -# 说明:事务等待获取资源等待的最长时间,单位为秒,看具体业务情况,一般默认值就好 -# 默认值:50,建议值:看业务。 -innodb_lock_wait_timeout = 60 - -# 说明:设置了 Mysql 后台任务(例如页刷新和 merge dadta from buffer pool)每秒 io 操作的上限。 -# 默认值:200,建议值:方法一,单盘 sata 设 100,sas10,raid10 设 200,ssd 设 2000,fushion-io 设 50000;方法二,通过测试工具获得磁盘 io 性能后,设置 IOPS 数值/2。 -innodb_io_capacity = 2000 - -# 说明:该参数是所有缓冲区线程 io 操作的总上限。 -# 默认值:innodb_io_capacity 的两倍。建议值:例如用 iometer 测试后的 iops 数值就好 -innodb_io_capacity_max = 4000 - -# 说明:控制着 innodb 数据文件及 redo log 的打开、刷写模式,三种模式:fdatasync(默认),O_DSYNC,O_DIRECT -# fdatasync:数据文件,buffer pool->os buffer->磁盘;日志文件,buffer pool->os buffer->磁盘; -# O_DSYNC: 数据文件,buffer pool->os buffer->磁盘;日志文件,buffer pool->磁盘; -# O_DIRECT: 数据文件,buffer pool->磁盘; 日志文件,buffer pool->os buffer->磁盘; -# 默认值为空,建议值:使用 SAN 或者 raid,建议用 O_DIRECT,不懂测试的话,默认生产上使用 O_DIRECT -innodb_flush_method = O_DIRECT - -# 说明:mysql5.7 之后默认开启,意思是,每张表一个独立表空间。 -# 默认值 1,开启 -innodb_file_per_table = 1 - -# 说明:The path where InnoDB creates undo tablespaces。通常等于 undo log 文件的存放目录。 -# 默认值 ./;自行设置 -innodb_undo_directory = /usr/local/mysql-5.7.21/log - -# 说明:The number of undo tablespaces used by InnoDB 等于 undo log 文件数量。5.7.21 后开始弃用 -# 默认值为 0,建议默认值就好,不用调整了。 -innodb_undo_tablespaces = 0 - -# 说明:定义 undo 使用的回滚段数量。5.7.19 后弃用 -# 默认值 128,建议不动,以后弃用了。 -innodb_undo_logs = 128 - -# 说明:5.7.5 后开始使用,在线收缩 undo log 使用的空间。 -# 默认值:关闭,建议值:开启 -innodb_undo_log_truncate = 1 - -# 说明:结合 innodb_undo_log_truncate,实现 undo 空间收缩功能 -# 默认值:1G,建议值,不改。 -innodb_max_undo_log_size = 1G - -# 说明:重作日志文件的存放目录 -innodb_log_group_home_dir = /usr/local/mysql-5.7.21/log - -# 说明:日志文件的大小 -# 默认值:48M,建议值:根据你系统的磁盘空间和日志增长情况调整大小 -innodb_log_file_size = 128M - -# 说明:日志组中的文件数量,mysql 以循环方式写入日志 -# 默认值 2,建议值:根据你系统的磁盘空间和日志增长情况调整大小 -innodb_log_files_in_group = 3 - -# 此参数确定些日志文件所用的内存大小,以 M 为单位。缓冲区更大能提高性能,但意外的故障将会丢失数据。MySQL 开发人员建议设置为 1-8M 之间 -innodb_log_buffer_size = 16M - -# 说明:可以控制 log 从系统 buffer 刷入磁盘文件的刷新频率,增大可减轻系统负荷 -# 默认值是 1;建议值不改。系统性能一般够用。 -innodb_flush_log_at_timeout = 1 - -# 说明:参数可设为 0,1,2; -# 参数 0:表示每秒将 log buffer 内容刷新到系统 buffer 中,再调用系统 flush 操作写入磁盘文件。 -# 参数 1:表示每次事务提交,redo log 都直接持久化到磁盘。 -# 参数 2:表示每次事务提交,隔 1 秒后再将 redo log 持久化到磁盘。 -# 建议设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。 -innodb_flush_log_at_trx_commit = 1 - -# 说明:限制 Innodb 能打开的表的数据,如果库里的表特别多的情况,请增加这个。 -# 值默认是 2000,建议值:参考数据库表总数再进行调整,一般够用不用调整。 -innodb_open_files = 8192 - -# innodb 处理 io 读写的后台并发线程数量,根据 cpu 核来确认,取值范围:1-64 -# 默认值:4,建议值:与逻辑 cpu 数量的一半保持一致。 -innodb_read_io_threads = 4 -innodb_write_io_threads = 4 - -# 默认设置为 0,表示不限制并发数,这里推荐设置为 0,更好去发挥 CPU 多核处理能力,提高并发量 -innodb_thread_concurrency = 0 - -# 默认值为 4,建议不变。InnoDB 中的清除操作是一类定期回收无用数据的操作。mysql 5.5 之后,支持多线程清除操作。 -innodb_purge_threads = 4 - -# 说明:mysql 缓冲区分为 new blocks 和 old blocks;此参数表示 old blocks 占比; -# 默认值:37,建议值,一般不动 -innodb_old_blocks_pct = 37 - -# 说明:新数据被载入缓冲池,进入 old pages 链区,当 1 秒后再次访问,则提升进入 new pages 链区。 -# 默认值:1000 -innodb_old_blocks_time=1000 - -# 说明:开启异步 io,可以提高并发性,默认开启。 -# 默认值为 1,建议不动 -innodb_use_native_aio = 1 - -# 说明:默认为空,使用 data 目录,一般不改。 -innodb_data_home_dir=/usr/local/mysql-5.7.21/data - -# 说明:Defines the name,size,and attributes of InnoDB system tablespace data files。 -# 默认值,不指定,默认为 ibdata1:12M:autoextend -innodb_data_file_path = ibdata1:12M:autoextend - -# 说明:设置了 InnoDB 存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,除非你的数据对象及其多,否则一般默认不改。 -# innodb_additional_mem_pool_size = 16M -# 说明:The crash recovery mode。只有紧急情况需要恢复数据的时候,才改为大于 1-6 之间数值,含义查下官网。 -# 默认值为 0; -#innodb_force_recovery = 0 - - - -[mysqldump] - -# quick 选项强制 mysqldump 从服务器查询取得记录直接输出而不是取得所有记录后将它们缓存到内存中 -quick - -max_allowed_packet = 16M - - - -[mysql] - -# mysql 命令行工具不使用自动补全功能,建议还是改为 -# no-auto-rehash -auto-rehash - -# socket 文件 -socket = /var/lib/mysql/mysql.sock -``` - -## 参考资料 - -- [《高性能 MySQL》](https://item.jd.com/11220393.html) -- [Mysql 配置文件/etc/my.cnf 解析](https://www.jianshu.com/p/5f39c486561b) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 02facdb92d..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Mysql 常见问题 -date: 2020-09-12 10:43:53 -categories: - - 数据库 - - 关系型数据库 - - Mysql -tags: - - 数据库 - - 关系型数据库 - - Mysql - - FAQ -permalink: /pages/7b0caf/ ---- - -# Mysql 常见问题 - -> **📦 本文以及示例源码已归档在 [db-tutorial](https://github.com/dunwu/db-tutorial/)** - -## 为什么表数据删掉一半,表文件大小不变 - -【问题】数据库占用空间太大,我把一个最大的表删掉了一半的数据,怎么表文件的大小还是没变? - -表数据既可以存在共享表空间里,也可以是单独的文件。这个行为是由参数 `innodb_file_per_table` 控制的: - -1. 这个参数设置为 OFF 表示的是,表的数据放在系统共享表空间,也就是跟数据字典放在一起; -2. 这个参数设置为 ON 表示的是,每个 InnoDB 表数据存储在一个以 .ibd 为后缀的文件中。 - -从 MySQL 5.6.6 版本开始,它的默认值就是 ON 了。 - -我建议你不论使用 MySQL 的哪个版本,都将这个值设置为 ON。因为,一个表单独存储为一个文件更容易管理,而且在你不需要这个表的时候,通过 drop table 命令,系统就会直接删除这个文件。而如果是放在共享表空间中,即使表删掉了,空间也是不会回收的。 - -所以,**将 innodb_file_per_table 设置为 ON,是推荐做法,我们接下来的讨论都是基于这个设置展开的。** - -我们在删除整个表的时候,可以使用 drop table 命令回收表空间。但是,我们遇到的更多的删除数据的场景是删除某些行,这时就遇到了我们文章开头的问题:表中的数据被删除了,但是表空间却没有被回收。 - -**插入和删除操作可能会造成空洞**。 - -- 插入时,如果插入位置所在页已满,需要申请新页面。 -- 删除时,不会删除所在页,而是将记录在页面的位置标记为可重用。 - -所以,如果能够把这些空洞去掉,就能达到收缩表空间的目的。 - -要达到收缩空洞的目的,可以使用重建表的方式。 - -## 参考资料 - -- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) -- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\351\235\242\350\257\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\351\235\242\350\257\225.md" new file mode 100644 index 0000000000..979abf49b3 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/99.Mysql\351\235\242\350\257\225.md" @@ -0,0 +1,1037 @@ +--- +icon: logos:mysql +title: Mysql 面试 +date: 2020-09-12 10:43:53 +order: 99 +categories: + - 数据库 + - 关系型数据库 + - Mysql +tags: + - 数据库 + - 关系型数据库 + - Mysql + - 面试 +permalink: /pages/7b0caf/ +--- + +# Mysql 面试 + +## 基础 + +### EXISTS 和 IN 有什么区别? + +- `EXISTS` - 先对外表进行循环查询,再将查询结果放入 `EXISTS` 的子查询中进行条件比较,确定外层查询数据是否保留; +- `IN` - 先查询内表,将内表的查询结果作为条件,提供给外表查询语句进行比较; + +索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。 + +比如下面这样: + +```sql + SELECT * FROM A WHERE cc IN (SELECT cc FROM B) + SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc=A.cc) +``` + +当 A 小于 B 时,用 `EXISTS`。因为 `EXISTS` 的实现,相当于外表循环,实现的逻辑类似于: + +```sql + for i in A + for j in B + if j.cc == i.cc then ... +``` + +当 B 小于 A 时用 `IN`,因为实现的逻辑类似于: + +```sql + for i in B + for j in A + if j.cc == i.cc then ... +``` + +哪个表小就用哪个表来驱动,A 表小就用 `EXISTS`,B 表小就用 `IN`;如果两个表大小相当,则使用 `EXISTS` 和 `IN` 的区别不大。 + +### UNION 和 UNION ALL 有什么区别? + +`UNION` 和 `UNION ALL` 都是将两个结果集合并为一个,**两个要联合的 SQL 语句字段个数必须一样,而且字段类型要“相容”(一致)**。 + +- `UNION` 需要进行去重扫描,因此消息较低;而 `UNION ALL` 不会进行去重。 +- `UNION` 会按照字段的顺序进行排序;而 `UNION ALL` 只是简单的将两个结果合并就返回。 + +### JOIN 有哪些类型? + +**在 SELECT, UPDATE 和 DELETE 语句中,“连接”可以用于联合多表查询。连接使用 `JOIN` 关键字,并且条件语句使用 `ON` 而不是 `WHERE`**。 + +**连接可以替换子查询,并且一般比子查询的效率更快**。 + +`JOIN` 有以下类型: + +- 内连接 - 内连接又称等值连接,用于获取两个表中字段匹配关系的记录,**使用 `INNER JOIN` 关键字**。在没有条件语句的情况下**返回笛卡尔积**。 + - 笛卡尔积 - **“笛卡尔积”也称为交叉连接(`CROSS JOIN`),它的作用就是可以把任意表进行连接,即使这两张表不相关**。 + - 自连接(=) - **“自连接(=)”可以看成内连接的一种,只是连接的表是自身而已**。 + - 自然连接(NATURAL JOIN) - **“自然连接”会自动连接所有同名列**。自然连接使用 `NATURAL JOIN` 关键字。 +- 外连接 + - 左连接(LEFT JOIN) - **“左外连接”会获取左表所有记录,即使右表没有对应匹配的记录**。左外连接使用 `LEFT JOIN` 关键字。 + - 右连接(RIGHT JOIN) - **“右外连接”会获取右表所有记录,即使左表没有对应匹配的记录**。右外连接使用 `RIGHT JOIN` 关键字。 + +![SQL JOIN](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/sql-join.png) + +### CHAR 和 VARCHAR 的区别是什么? + +CHAR 和 VARCHAR 的主要区别在于:**CHAR 是定长字符串,VARCHAR 是变长字符串。** + +- CHAR 在存储时会在右边填充空格以达到指定的长度,检索时会去掉空格;VARCHAR 在存储时需要使用 1 或 2 个额外字节记录字符串的长度,检索时不需要处理。 +- CHAR 更适合存储长度较短或者长度都差不多的字符串,例如 Bcrypt 算法、MD5 算法加密后的密码、身份证号码。VARCHAR 类型适合存储长度不确定或者差异较大的字符串,例如用户昵称、文章标题等。 +- CHAR(M) 和 VARCHAR(M) 的 M 都代表能够保存的字符数的最大值,无论是字母、数字还是中文,每个都只占用一个字符。 + +### 金钱相关的数据用什么类型存储? + +MySQL 中有 3 种类型可以表示浮点数,分别是 `float`、`double` 和 `decimal`。 + +> float 和 double 为什么会丢失精度? + +**采用 float 和 double 类型会丢失精度**。数据的精确度取决于分配给每种数据类型的存储长度。由于计算机只能存储二进制,所以浮点型数据在存储的时候,必须转化成二进制。 + +- 单精度类型 float 存储空间为 4 字节,即 32 位。 +- 双精度类型 double 存储空间为 8 字节,即 64 位。 + +如果存储的数据转为二进制后,超过存储的位数,数据就被截断,因此存在丢失精度的可能。 + +【示例】丢失精度案例 + +```sql +-- 创建表 +CREATE TABLE `test` ( + `value` float(10,2) DEFAULT NULL +); + +mysql> insert into test value (131072.32); +Query OK, 1 row affected (0.01 sec) + +mysql> select * from test; ++-----------+ +| value | ++-----------+ +| 131072.31 | ++-----------+ +1 row in set (0.02 sec) +``` + +说明:示例中,使用 float 类型,明明保留了两位小数。但是写入的数据却从 `131072.32` 变成了 `131072.31` 。 + +> 选择什么类型可以不丢失精度? + +`decimal` 类型是 MySQL 官方唯一指定能精确存储的类型。因此,对于不允许丢失精度的场景(如金钱相关的业务),请务必使用 `decimal` 类型。 + +> 扩展阅读:[MySQL 如何选择 float, double, decimal](http://blog.leanote.com/post/weibo-007/mysql_float_double_decimal) + +### 如何存储 emoji 😃? + +Mysql 中的默认字符集为 utf8,无法存储 emoji,如果要存储 emoji,必须使用 utf8mb4 字符集。 + +设置 utf8mb4 字符集方法如下: + +```sql +ALTER TABLE test +DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +``` + +### 什么是范式?什么是反范式? + +> 什么是范式? + +数据库规范化,又称“**范式**”,是数据库设计的指导理论。**范式的目标是:使数据库结构更合理,消除存储异常,使数据冗余尽量小,增进数据的一致性**。 + +根据约束程度从低到高有:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)等等。现代数据库设计,一般最多满足 3NF——范式过高,虽然具有对数据关系更好的约束性,但也导致数据关系表增加而令数据库 IO 更繁忙。因此,在实际应用中,本来可以交由数据库处理的关系约束,很多都是在数据库使用程序中完成的。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030715177.png) + +> 什么是三大范式? + +三大范式,从低到高,依次为: + +- 1NF 要求属性具有原子性,不可再分解。 +- 2NF 要求记录有唯一标识,即实体的唯一性,即**不存在部分依赖**。 +- 3NF 是对字段的**冗余性**,要求任何字段不能由其他字段派生出来,它要求字段没有冗余,即**不存在传递依赖**。 + +现代数据库设计,一般最多满足 3NF——范式过高,虽然具有对数据关系更好的约束性,但也导致数据关系表增加而令数据库 IO 更繁忙。因此,在实际应用中,本来可以交由数据库处理的关系约束,很多都是在数据库使用程序中完成的。 + +> 什么是反范式? + +范式和反范式: + +- 范式 - 消除冗余 +- 反范式 - 适当冗余数据,以提高查询效率——空间换时间 + +## 架构 + +### 一条 SQL 查询语句是如何执行的? + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030712357.png) + +1. **连接器**:连接器负责跟客户端建立连接、获取权限、维持和管理连接。 +2. **查询缓存**:命中缓存,则直接返回结果。弊大于利,因为失效非常频繁——任何更新都会清空查询缓存。 +3. **分析器** + - **词法分析**:解析 SQL 关键字 + - **语法分析**:生成一颗对应的语法解析树 +4. **优化器** + - 根据语法树**生成多种执行计划** + - **索引选择**:根据策略选择最优方式 +5. **执行器** + - 校验读写权限 + - 根据执行计划,调用存储引擎的 API 来执行查询 +6. **存储引擎**:存储数据,提供读写接口 + +### 一条 SQL 更新语句是如何执行的? + +更新流程和查询的流程大致相同,不同之处在于:更新流程还涉及两个重要的日志模块: + +- redo log(重做日志) + - InnoDB 存储引擎独有的日志(物理日志) + - 采用循环写入 +- bin log(归档日志) + - Mysql Server 层通用日志(逻辑日志) + - 采用追加写入 + +为了保证 redo log 和 bin log 的数据一致性,所以采用两阶段提交方式更新日志。 + +### 一条 SQL 查询语句的执行顺序是怎样的? + +一条完整的 SELECT 语句内部的执行顺序是这样的: + +1. **FROM** - 对 FROM 子句中的左表 `` 和右表 `` 执行笛卡儿积(Cartesianproduct),产生虚拟表 VT1 +2. **ON** - 对虚拟表 VT1 应用 ON 筛选,只有那些符合 `` 的行才被插入虚拟表 VT2 中 +3. **JOIN** - 如果指定了 OUTER JOIN(如 LEFT OUTER JOIN、RIGHT OUTER JOIN),那么保留表中未匹配的行作为外部行添加到虚拟表 VT2 中,产生虚拟表 VT3。如果 FROM 子句包含两个以上表,则对上一个连接生成的结果表 VT3 和下一个表重复执行步骤 1)~步骤 3),直到处理完所有的表为止 +4. **WHERE** - 对虚拟表 VT3 应用 WHERE 过滤条件,只有符合 `` 的记录才被插入虚拟表 VT4 中 +5. **GROUP BY** - 根据 GROUP BY 子句中的列,对 VT4 中的记录进行分组操作,产生 VT5 +6. **CUBE|ROLLUP** - 对表 VT5 进行 CUBE 或 ROLLUP 操作,产生表 VT6 +7. **HAVING** - 对虚拟表 VT6 应用 HAVING 过滤器,只有符合 `` 的记录才被插入虚拟表 VT7 中。 +8. **SELECT** - 第二次执行 SELECT 操作,选择指定的列,插入到虚拟表 VT8 中 +9. **DISTINCT** - 去除重复数据,产生虚拟表 VT9 +10. **ORDER BY** - 将虚拟表 VT9 中的记录按照 `` 进行排序操作,产生虚拟表 VT10。11) +11. **LIMIT** - 取出指定行的记录,产生虚拟表 VT11,并返回给查询用户 + +## 存储引擎 + +### Mysql 有哪些常见存储引擎? + +- **InnoDB** - Mysql 的默认存储引擎。支持事务、外键、表级锁和行级锁、自动崩溃恢复。索引采用 B+ 树聚簇索引。 +- **MyISAM** - Mysql 5.1 版本前的默认存储引擎。特性丰富,但不支持事务、外键、行级锁、自动崩溃恢复。索引采用 B+ 树非聚簇索引。 +- **Memory** - 适合快速访问数据,且数据不会被修改,重启丢失也没有关系。 +- **Archive** - 适合存储归档数据。 +- **NDB** - 用于 Mysql 集群场景。 +- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。 + +Mysql 中同一个数据库中的不同表可以设置不同的存储引擎。 + +### InnoDB 和 MyISAM 有哪些差异? + +| 对比项 | MyISAM | InnoDB | +| -------- | --------------------------------------------- | ---------------------------- | +| 主外键 | 不支持 | 支持 | +| 事务 | 不支持 | 支持四种事务隔离级别 | +| 锁 | 支持表级锁 | 支持表级锁、行级锁 | +| 索引 | 采用 B+ 树索引(非聚簇索引) | 采用 B+ 树索引(聚簇索引) | +| 表空间 | 小 | 大 | +| 关注点 | 性能 | 事务 | +| 计数器 | 维护了计数器,`SELECT COUNT(*)` 效率为 `O(1)` | 没有维护计数器,需要全表扫描 | +| 故障恢复 | 不支持 | 支持(依赖于 redo log) | + +### 如何选择存储引擎? + +- 大多数情况下,使用默认的 InnoDB 就够了。如果要提供提交、回滚和恢复的事务安全(ACID 兼容)能力,并要求实现并发控制,InnoDB 就是比较靠前的选择了。 +- 如果数据表主要用来插入和查询记录,则 MyISAM 引擎提供较高的处理效率。 +- 如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存的 MEMORY 引擎中,MySQL 中使用该引擎作为临时表,存放查询的中间结果。 +- 如果存储归档数据,可以使用 ARCHIVE 引擎。 + +使用哪一种引擎可以根据需要灵活选择,因为存储引擎是基于表的,所以一个数据库中多个表可以使用不同的引擎以满足各种性能和实际需求。使用合适的存储引擎将会提高整个数据库的性能。 + +## 日志 + +### MySQL 有哪些类型的日志? + +MySQL 日志文件有很多,包括 : + +- **错误日志**(error log):错误日志文件对 MySQL 的启动、运行、关闭过程进行了记录,能帮助定位 MySQL 问题。 +- **慢查询日志**(slow query log):慢查询日志是用来记录执行时间超过 long_query_time 这个变量定义的时长的查询语句。通过慢查询日志,可以查找出哪些查询语句的执行效率很低,以便进行优化。 +- **一般查询日志**(general log):一般查询日志记录了所有对 MySQL 数据库请求的信息,无论请求是否正确执行。 +- **二进制日志**(bin log):关于二进制日志,它记录了数据库所有执行的 DDL 和 DML 语句(除了数据查询语句 select、show 等),以事件形式记录并保存在二进制文件中。 + +还有两个 InnoDB 存储引擎特有的日志文件: + +- **重做日志**(redo log):重做日志至关重要,因为它们记录了对于 InnoDB 存储引擎的事务日志。 +- **回滚日志**(undo log):回滚日志同样也是 InnoDB 引擎提供的日志,顾名思义,回滚日志的作用就是对数据进行回滚。当事务对数据库进行修改,InnoDB 引擎不仅会记录 redo log,还会生成对应的 undo log 日志;如果事务执行失败或调用了 rollback,导致事务需要回滚,就可以利用 undo log 中的信息将数据回滚到修改之前的样子。 + +### bin log 和 redo log 有什么区别? + +- bin log 会记录所有与数据库有关的日志记录,包括 InnoDB、MyISAM 等存储引擎的日志;而 redo log 只记 InnoDB 存储引擎的日志。 +- 记录的内容不同,bin log 记录的是关于一个事务的具体操作内容,即该日志是逻辑日志。而 redo log 记录的是关于每个页(Page)的更改的物理情况。 +- 写入的时间不同,bin log 仅在事务提交前进行提交,也就是只写磁盘一次。而在事务进行的过程中,却不断有 redo ertry 被写入 redo log 中。 +- 写入的方式也不相同,redo log 是循环写入和擦除,bin log 是追加写入,不会覆盖已经写的文件。 + +### redo log 如何刷盘? + +redo log 的写入不是直接落到磁盘,而是在内存中设置了一片称之为 redo log buffer 的连续内存空间,也就是 redo 日志缓冲区。 + +在如下的一些情况中,log buffer 的数据会刷入磁盘: + +- log buffer 空间不足时 + +log buffer 的大小是有限的,如果不停的往这个有限大小的 log buffer 里塞入日志,很快它就会被填满。如果当前写入 log buffer 的 redo 日志量已经占满了 log buffer 总容量的大约**一半**左右,就需要把这些日志刷新到磁盘上。 + +- 事务提交时 + +在事务提交时,为了保证持久性,会把 log buffer 中的日志全部刷到磁盘。注意,这时候,除了本事务的,可能还会刷入其它事务的日志。 + +- 后台线程输入 + +有一个后台线程,大约每秒都会刷新一次`log buffer`中的`redo log`到磁盘。 + +- 正常关闭服务器时 +- **触发 checkpoint 规则** + +重做日志缓存、重做日志文件都是以**块(block)\*\*的方式进行保存的,称之为\*\*重做日志块(redo log block)**,块的大小是固定的 512 字节。我们的 redo log 它是固定大小的,可以看作是一个逻辑上的 **log group**,由一定数量的**log block** 组成。 + +它的写入方式是从头到尾开始写,写到末尾又回到开头循环写。 + +其中有两个标记位置: + +`write pos`是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。`checkpoint`是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到磁盘。 + +当`write_pos`追上`checkpoint`时,表示 redo log 日志已经写满。这时候就不能接着往里写数据了,需要执行`checkpoint`规则腾出可写空间。 + +所谓的**checkpoint 规则**,就是 checkpoint 触发后,将 buffer 中日志页都刷到磁盘。 + +### 日志为什么要两阶段提交? + +## 索引 + +### 什么是索引?为什么要使用索引? + +**“索引”是数据库为了提高查找效率的一种数据结构**。 + +日常生活中,我们可以通过检索目录,来快速定位书本中的内容。索引和数据表,就好比目录和书,想要高效查询数据表,索引至关重要。在数据量小且负载较低时,不恰当的索引对于性能的影响可能还不明显;但随着数据量逐渐增大,性能则会急剧下降。因此,**设置合理的索引是数据库查询性能优化的最有效手段**。 + +### 索引的优点和缺点是什么? + +✔️️️️️️️ 索引的优点: + +- **索引大大减少了服务器需要扫描的数据量**,从而加快检索速度。 +- **索引可以帮助服务器避免排序和临时表**。 +- **索引可以将随机 I/O 变为顺序 I/O**。 +- 支持行级锁的数据库,如 InnoDB 会在访问行的时候加锁。**使用索引可以减少访问的行数,从而减少锁的竞争,提高并发**。 +- 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。 + +❌ 索引的缺点: + +- **创建和维护索引要耗费时间**,这会随着数据量的增加而增加。 +- **索引需要占用额外的物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 +- **写操作(`INSERT`/`UPDATE`/`DELETE`)时很可能需要更新索引,导致数据库的写操作性能降低**。 + +基于以上,可以归纳出索引的基本使用规则: + +- 索引不是越多越好,不要为所有列都创建索引 +- 要尽量避免冗余和重复索引 +- 要考虑删除未使用的索引 +- 尽量的扩展索引,不要新建索引 +- 频繁作为 `WHERE` 过滤条件的列应该考虑添加索引 + +### 何时适用索引?何时不适用索引? + +✔️️️️ 什么情况**适用**索引? + +- **字段的数值有唯一性的限制**,如用户名。 +- **频繁作为 `WHERE` 条件或 `JOIN` 条件的字段,尤其在数据表大的情况下**。 +- **频繁用于 `GROUP BY` 或 `ORDER BY` 的字段**。将该字段作为索引,查询时就无需再排序了,因为 B+ 树本身就是按序存储的。 +- **DISTINCT 字段需要创建索引**。 + +❌ 什么情况**不适用**索引? + +- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` ),也就意味着需要更新索引。 +- **很少作为 `WHERE` 条件或 `JOIN` 条件的字段**,也就意味着索引会经常无法命中,没有意义,还增加空间开销。 +- **非常小的表**,对于非常小的表,大部分情况下简单的全表扫描更高效。 +- **特大型的表**,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 + +### 索引如何分类? + +索引可以从不同维度来分类: + +- 数据结构 + - B+tree 索引 + - Hash 索引 + - Full-text 索引 +- 物理存储 + - 聚簇索引(主键索引) + - 二级索引(辅助索引) +- 字段特性 + - 主键索引(`PRIMARY`) + - 唯一索引(`UNIQUE`) + - 普通索引(`INDEX`) + - 前缀索引 +- 字段个数 + - 单列索引 + - 联合索引 + +### 索引有哪些常见数据结构? + +在 Mysql 中,**索引是在存储引擎层而不是服务器层实现的**,所以,并没有统一的索引标准。不同存储引擎的索引的数据结构也不相同。下面是 Mysql 常用存储引擎对一些主要索引数据结构的支持: + +| 索引数据结构/存储引擎 | InnoDB 引擎 | MyISAM 引擎 | Memory 引擎 | +| --------------------- | ----------- | ----------- | ----------- | +| **B+ 树索引** | ✔️️️️️️️ | ✔️️️️️️️ | ✔️️️️️️️ | +| **Hash 索引** | ❌ | ❌ | ✔️️️️️️️ | +| **Full Text 索引** | ✔️️️️️️️ | ✔️️️️️️️ | ❌ | + +Mysql 索引的常见数据结构: + +- **哈希索引** + - 因为索引数据结构紧凑,所以**查询速度非常快**。 + - **只支持等值比较查询** - 包括 `=`、`IN()`、`<=>`;**不支持任何范围查询**,如 `WHERE price > 100`。 + - **无法用于排序** - 因为哈希索引数据不是按照索引值顺序存储的。 + - **不支持部分索引匹配查找** - 因为哈希索引时使用索引列的全部内容来进行哈希计算的。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 + - **不能用索引中的值来避免读取行** - 因为哈希索引只包含哈希值和行指针,不存储字段,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响不大。 + - 哈希索引有**可能出现哈希冲突** + - 出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 + - 如果哈希冲突多的话,维护索引的代价会很高。 +- B 树索引 + - 适用于**全键值查找**、**键值范围查找**和**键前缀查找**,其中键前缀查找只适用于最左前缀查找。 + - 所有的关键字(可以理解为数据)都存储在叶子节点,非叶子节点并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上。 + - 所有的叶子节点由指针连接。 + +### 为什么 InnoDB 索引采用 B+ 树? + +> B+ 树 vs B 树 + +- B+ 树只在叶子节点存储数据,而 B 树的非叶子节点也要存储数据,所以 B+ 树的单个节点的数据量更小,在相同的磁盘 I/O 次数下,就能查询更多的节点。 +- 另外,B+ 树叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而 B 树无法做到这一点。 + +> B+ 树 vs 二叉树 + +- 对于有 N 个叶子节点的 B+ 树,其搜索复杂度为 `O(logdN)`,其中 d 表示节点允许的最大子节点个数为 d 个。 +- 在实际的应用当中, d 值是大于 100 的,这样就保证了,即使数据达到千万级别时,B+ 树的高度依然维持在 1~3 层左右,也就是说一次数据查询操作只需要做 1~3 次的磁盘 I/O 操作就能查询到目标数据。 +- 而二叉树的每个父节点的儿子节点个数只能是 2 个,意味着其搜索复杂度为 `O(logN)`,这已经比 B+ 树高出不少,因此二叉树检索到目标数据所经历的磁盘 I/O 次数要更多。 + +一言以蔽之,使用 B+ 树,而不是二叉树,是为了减少树的高度,也就是为了减少磁盘 I/O 次数。 + +> B+ 树索引和 Hash 索引的差异 + +- **B+ 树索引支持范围查询**;Hash 索引不支持。 +- **B+ 树索引支持联合索引的最左匹配原则**;Hash 索引不支持。 +- **B+ 树索引支持排序**;Hash 索引不支持。 +- **B+ 树索引支持模糊查询**;Hash 索引不支持。 +- Hash 索引的等值查询比 B+ 树索引效率高。 + +综上,Hash 索引的应用场景很苛刻,不适用于绝大多数场景。 + +### 聚簇索引和非聚簇索引有什么区别? + +根据叶子节点的内容,索引类型分为主键索引和非主键索引。 + +主键索引又被称为**“聚簇索引(clustered index)”,其叶子节点存的是整行数据**。 + +- 聚簇表示数据行和相邻的键值紧凑地存储在一起,因为数据紧凑,所以访问快。 +- 因为无法同时把数据行存放在两个不同的地方,所以**一个表只能有一个聚簇索引**。 +- InnoDB 的聚簇索引实际是在同一个结构中保存了 B 树的索引和数据行。 + +非主键索引又被称为**“二级索引(secondary index)”,其叶子节点存的是主键的值**。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。 + +- 如果语句是 `select * from T where ID=500`,即聚簇索引查询方式,则只需要搜索主键索引树; +- 如果语句是 `select * from T where k=5`,即非聚簇索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为**回表**。 + +也就是说,**基于非聚簇索引的查询需要多扫描一棵索引树**。因此,我们在应用中应该尽量使用主键查询。 + +**显然,主键长度越小,非聚簇索引的叶子节点就越小,非聚簇索引占用的空间也就越小。** + +### 索引有哪些优化策略? + +#### 索引基本原则 + +- **索引不是越多越好,不要为所有列都创建索引**。要考虑到索引的维护代价、空间占用和查询时回表的代价。索引一定是按需创建的,并且要尽可能确保足够轻量。一旦创建了多字段的联合索引,我们要考虑尽可能利用索引本身完成数据查询,减少回表的成本。 +- 要**尽量避免冗余和重复索引**。 +- 要**考虑删除未使用的索引**。 +- **尽量的扩展索引,不要新建索引**。 +- **频繁作为 `WHERE` 过滤条件的列应该考虑添加索引**。 + +#### 覆盖索引 + +**覆盖索引是指:索引上的信息足够满足查询请求,不需要回表查询数据**。 + +**由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段**。 + +#### 最左匹配原则 + +**这里的最左前缀,可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符**。 + +如果是联合索引,那么 key 也由多个列组成,同时,索引只能用于查找 key 是否**存在(相等)**,遇到范围查询 (`>`、`<`、`BETWEEN`、`LIKE`) 就**不能进一步匹配**了,后续退化为线性查找。因此,**列的排列顺序决定了可命中索引的列数**。 + +**应该将选择性高的列或基数大的列优先排在多列索引最前列**。**“索引的选择性”是指不重复的索引值和记录总数的比值**,选择性越高,查询效率越高。但有时,也需要考虑 `WHERE` 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。 + +#### 前缀索引 + +**“前缀索引”是指索引开始的部分字符**。对于 `BLOB`/`TEXT`/`VARCHAR` 这种文本类型的列,必须使用前缀索引,因为数据库往往不允许索引这些列的完整长度。 + +前缀索引的优点是可以**大大节约索引空间**,从而**提高索引效率**。 + +前缀索引的缺点是**会降低索引的区分度**。此外,**`order by` 无法使用前缀索引,无法把前缀索引用作覆盖索引**。 + +#### 使用索引来排序 + +Mysql 有两种方式可以生成排序结果:通过排序操作;或者按索引顺序扫描。 + +**索引最好既满足排序,又用于查找行**。这样,就可以通过命中覆盖索引直接将结果查出来,也就不再需要排序了。 + +这样整个查询语句的执行流程就变成了: + +1. 从索引 (city,name,age) 找到第一个满足 city='杭州’条件的记录,取出其中的 city、name 和 age 这三个字段的值,作为结果集的一部分直接返回; +2. 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回; +3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。 + +#### = 和 in 可以乱序 + +**不需要考虑 `=`、`IN` 等的顺序**,Mysql 会自动优化这些条件的顺序,以匹配尽可能多的索引列。 + +【示例】如有索引 (a, b, c, d),查询条件 `c > 3 and b = 2 and a = 1 and d < 4` 与 `a = 1 and c > 3 and b = 2 and d < 4` 等顺序都是可以的,MySQL 会自动优化为 a = 1 and b = 2 and c > 3 and d < 4,依次命中 a、b、c、d。 + +### 哪些情况下,索引会失效? + +导致索引失效的情况有: + +- 对索引使用左模糊匹配 +- 对索引使用函数或表达式 +- 对索引隐式类型转换 +- 联合索引不遵循最左匹配原则 +- 索引列判空 - 索引列与 NULL 或者 NOT NULL 进行判断的时候也会失效 +- WHERE 子句中的 OR + +### 普通索引和唯一索引,应该怎么选择? + +普通索引和唯一索引的**查询性能相差微乎其微**。 + +## 事务 + +### 什么是事务,什么是 ACID? + +**“事务”指的是满足 ACID 特性的一组操作**。事务内的 SQL 语句,要么全执行成功,要么全执行失败。可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030701337.png) + +ACID 是数据库事务正确执行的四个基本要素。 + +- **原子性(Atomicity)** + - 事务被视为不可分割的最小单元,事务中的所有操作要么全部提交成功,要么全部失败回滚。 + - 回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 +- **一致性(Consistency)** + - 数据库在事务执行前后都保持一致性状态。 + - 在一致性状态下,所有事务对一个数据的读取结果都是相同的。 +- **隔离性(Isolation)** + - 一个事务所做的修改在最终提交以前,对其它事务是不可见的。 +- **持久性(Durability)** + - 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 + - 可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 + +一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性。 + +### 事务存在哪些并发一致性问题? + +事务中存在的并发一致性问题有: + +- 丢失修改 +- 脏读 +- 不可重复读 +- 幻读 + +**“丢失修改”是指一个事务的更新操作被另外一个事务的更新操作替换**。 + +如下图所示,T1 和 T2 两个事务对同一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706586.png) + +**“脏读(dirty read)”是指当前事务可以读取其他事务未提交的数据**。 + +如下图所示,T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706587.png) + +**“不可重复读(non-repeatable read)”是指一个事务内多次读取同一数据,过程中,该数据被其他事务所修改,导致当前事务多次读取的数据可能不一致**。 + +如下图所示,T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706588.png) + +**“幻读(phantom read)”是指一个事务内多次读取同一范围的数据,过程中,其他事务在该数据范围新增了数据,导致当前事务未发现新增数据**。 + +事务 T1 读取某个范围内的记录时,事务 T2 在该范围内插入了新的记录,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030706589.png) + +### 有哪些事务隔离级别,分别解决了什么问题? + +为了解决以上提到的并发一致性问题,SQL 标准提出了四种“事务隔离级别”来应对这些问题。事务隔离级别等级越高,越能保证数据的一致性和完整性,但是执行效率也越低。因此,设置数据库的事务隔离级别时需要做一下权衡。 + +事务隔离级别从低到高分别是: + +- **“读未提交(read uncommitted)”** - 是指,**事务中的修改,即使没有提交,对其它事务也是可见的**。 +- **“读已提交(read committed)” ** - 是指,**事务提交后,其他事务才能看到它的修改**。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 + - **读已提交解决了脏读的问题**。 + - 读已提交是大多数数据库的默认事务隔离级别,如 Oracle。 +- **“可重复读(repeatable read)”** - 是指:**保证在同一个事务中多次读取同样数据的结果是一样的**。 + - **可重复读解决了不可重复读问题**。 + - **可重复读是 InnoDB 存储引擎的默认事务隔离级别**。 +- **“串行化(serializable )”** - 是指,**强制事务串行执行**,对于同一行记录,加读写锁,一旦出现锁冲突,必须等前面的事务释放锁。 + - **串行化解决了幻读问题**。由于强制事务串行执行,自然避免了所有的并发问题。 + - **串行化策略会在读取的每一行数据上都加锁**,这可能导致大量的超时和锁竞争。这对于高并发应用基本上是不可接受的,所以一般不会采用这个级别。 + +事务隔离级别对并发一致性问题的解决情况: + +| 隔离级别 | 丢失修改 | 脏读 | 不可重复读 | 幻读 | +| :------: | :------: | :--: | :--------: | :--: | +| 读未提交 | ✔️️️ | ❌ | ❌ | ❌ | +| 读已提交 | ✔️️️ | ✔️️️ | ❌ | ❌ | +| 可重复读 | ✔️️️ | ✔️️️ | ✔️️️ | ❌ | +| 可串行化 | ✔️️️ | ✔️️️ | ✔️️️ | ✔️️️ | + +### 各事务隔离级别是如何实现的? + +Mysql 中的事务功能是在存储引擎层实现的,**并非所有存储引擎都支持事务功能**。InnoDB 是 Mysql 的首先事务存储引擎。 + +四种隔离级别具体是如何实现的呢? + +以 InnoDB 的事务实现来说明: + +- 对于“读未提交”隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了; +- 对于“串行化”隔离级别的事务来说,通过加读写锁的方式来避免并行访问; +- 对于“读提交”和“可重复读”隔离级别的事务来说,它们都是通过 ReadView 来实现的,区别仅在于创建 ReadView 的时机不同。ReadView 可以理解为一个数据快照。 + - “读提交”隔离级别是在“每个语句执行前”都会重新生成一个 ReadView + - “可重复读”隔离级别是在“启动事务时”生成一个 ReadView,然后整个事务期间都在用这个 ReadView。 + +### 什么是 MVCC? + +**MVCC 是 Multi Version Concurrency Control 的缩写,即“多版本并发控制”**。MVCC 的设计目标是提高数据库的并发性,采用非阻塞的方式去处理读/写并发冲突,可以将其看成一种乐观锁。 + +不仅是 Mysql,包括 Oracle、PostgreSQL 等其他关系型数据库都实现了各自的 MVCC,实现机制没有统一标准。**MVCC 是 InnoDB 存储引擎实现事务隔离级别的一种具体方式**。其主要用于实现读已提交和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 + +### MVCC 的实现原理是什么? + +MVCC 的实现原理,主要基于隐式字段、UndoLog、ReadView 来实现。 + +#### 隐式字段 + +InnoDB 存储引擎中,数据表的每行记录,除了用户显示定义的字段以外,还有几个数据库隐式定义的字段: + +- `row_id` - **隐藏的自增 ID**。如果数据表没有指定主键,InnoDB 会自动基于 `row_id` 产生一个聚簇索引。 +- `trx_id` - **最近修改的事务 ID**。事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里; +- `roll_pointer` - **回滚指针**,指向这条记录的上一个版本。 + +#### UndoLog + +MVCC 的多版本指的是多个版本的快照,快照存储在 UndoLog 中。该日志通过回滚指针 `roll_pointer` 把一个数据行的所有快照链接起来,构成一个**版本链**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030708591.png) + +#### ReadView + +**ReadView 就是事务进行快照读时产生的读视图(快照)**。 + +ReadView 有四个重要的字段: + +- `m_ids` - 指的是在创建 ReadView 时,当前数据库中“活跃事务”的事务 ID 列表。注意:这是一个列表,**“活跃事务”指的就是,启动了但还没提交的事务**。 +- `min_trx_id` - 指的是在创建 ReadView 时,当前数据库中“活跃事务”中事务 id 最小的事务,也就是 `m_ids` 的最小值。 +- `max_trx_id` - 这个并不是 m_ids 的最大值,而是指创建 ReadView 时当前数据库中应该给下一个事务分配的 ID 值,也就是全局事务中最大的事务 ID 值 + 1; +- `creator_trx_id` - 指的是创建该 ReadView 的事务的事务 ID。 + +在创建 ReadView 后,我们可以将记录中的 trx_id 划分为三种情况: + +- 已提交事务 +- 已启动但未提交的事务 +- 未启动的事务 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030708265.png) + +> ReadView 如何判断版本链中哪个版本可见? + +一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况: + +- `trx_id == creator_trx_id` - 表示 `trx_id` 版本记录由 ReadView 所代表的当前事务产生,当然可以访问。 +- `trx_id < min_trx_id` - 表示 `trx_id` 版本记录是在创建 ReadView 之前已提交的事务生成的,当前事务可以访问。 +- `trx_id >= max_trx_id` - 表示 `trx_id` 版本记录是在创建 ReadView 之后才启动的事务生成的,当前事务不可以访问。 +- `min_trx_id <= trx_id < max_trx_id` - 需要判断 `trx_id` 是否在 `m_ids` 列表中 + - 如果 `trx_id` 在 `m_ids` 列表中,表示生成 `trx_id` 版本记录的事务依然活跃(未提交事务),当前事务不可以访问。 + - 如果 `trx_id` 不在 `m_ids` 列表中,表示生成 `trx_id` 版本记录的事务已提交,当前事务可以访问。 + +这种通过“版本链”来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。 + +### MVCC 实现了哪些隔离级别,如何实现的? + +对于“读已提交”和“可重复读”隔离级别的事务来说,它们都是通过 MVCC 的 ReadView 机制来实现的,区别仅在于创建 ReadView 的时机不同。ReadView 可以理解为一个数据快照。 + +> MVCC 如何实现可重复读隔离级别 + +**可重复读隔离级别只有在启动事务时才会创建 ReadView,然后整个事务期间都使用这个 ReadView**。这样就保证了在事务期间读到的数据都是事务启动前的记录。 + +举个例子,假设有两个事务依次执行以下操作: + +- 初始,表中 id = 1 的 value 列值为 100。 +- 事务 2 读取数据,value 为 100; +- 事务 1 将 value 设为 200; +- 事务 2 读取数据,value 为 100; +- 事务 1 提交事务; +- 事务 2 读取数据,value 依旧为 100; + +以上操作,如下图所示。T2 事务在事务过程中,是否可以看到 T1 事务的修改,可以根据 [ReadView](#ReadView) 中描述的规则去判断。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030709235.png) + +从图中不难看出: + +- 对于 `trx_id = 100` 的版本记录,比对 T2 事务 ReadView ,`trx_id < min_trx_id`,因此在 T2 事务中的任意时刻都可见; +- 对于 `trx_id = 101` 的版本记录,比对 T2 事务 ReadView ,可以看出 `min_trx_id <= trx_id < max_trx_id` ,且 `trx_id` 在 `m_ids` 中,因此 T2 事务中不可见。 + +综上所述,在 T2 事务中,自始至终只能看到 `trx_id = 100` 的版本记录。 + +> MVCC 如何实现读已提交隔离级别 + +**读已提交隔离级别每次读取数据时都会创建一个 ReadView**。这意味着,事务期间的多次读取同一条数据,前后读取的数据可能会出现不一致——因为,这期间可能有另外一个事务修改了该记录,并提交了事务。 + +举个例子,假设有两个事务依次执行以下操作: + +- 初始,表中 id = 1 的 value 列值为 100。 +- 事务 2 读取数据(创建 ReadView),value 为 0; +- 事务 1 将 value 设为 100; +- 事务 2 读取数据(创建 ReadView),value 为 0; +- 事务 1 提交事务; +- 事务 2 读取数据(创建 ReadView),value 为 100; + +以上操作,如下图所示,T2 事务在事务过程中,是否可以看到其他事务的修改,可以根据 [ReadView](#ReadView) 中描述的规则去判断。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202311030709986.png) + +从图中不难看出: + +- 对于 `trx_id = 100` 的版本记录,比对 T2 事务 ReadView ,`trx_id < min_trx_id`,因此在 T2 事务中的任意时刻都可见; +- 对于 `trx_id = 101` 的版本记录,比对 T2 事务 ReadView ,可以看出第二次查询时(T1 更新未提交),`min_trx_id <= trx_id < max_trx_id` ,且 `trx_id` 在 `m_ids` 中,因此 T2 事务中不可见;而第三次查询时(T1 更新已提交),`trx_id < min_trx_id`,因此在 T2 事务中可见; + +综上所述,在 T2 事务中,当 T1 事务提交前,可读取到的是 `trx_id = 100` 的版本记录;当 T1 事务提交后,可读取到的是 `trx_id = 101` 的版本记录。 + +> MVCC + Next-Key Lock 如何解决幻读 + +MySQL InnoDB 引擎的默认隔离级别虽然是“可重复读”,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种: + +- 针对**快照读**(普通 `SELECT` 语句),**通过 MVCC 方式解决了幻读**,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。 +- 针对**当前读**(`SELECT ... FOR UPDATE` 等语句),**通过 Next-Key Lock(记录锁+间隙锁)方式解决了幻读**,因为当执行 `SELECT ... FOR UPDATE` 语句的时候,会加上 Next-Key Lock,如果有其他事务在 Next-Key Lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好的避免了幻读问题。 + +## 锁 + +### Mysql 中有哪些锁? + +为了解决并发一致性问题,Mysql 支持了很多种锁来实现不同程度的隔离性,以保证数据的安全性。 + +#### 独享锁和共享锁 + +InnoDB 实现标准行级锁定,根据是否独享资源,可以把锁分为两类: + +- **独享锁(Exclusive)**,简写为 X 锁,又称为“**写锁**”、“**排它锁**”。 + - 独享锁锁定的数据只允许进行锁定操作的事务使用,其他事务无法对已锁定的数据进行查询或修改。 + - 使用方式:`SELECT ... FOR UPDATE;` +- **共享锁(Shared)**,简写为 S 锁,又称为“**读锁**”。 + - 共享锁锁定的资源可以被其他用户读取,但不能修改。在进行 `SELECT` 的时候,会将对象进行共享锁锁定,当数据读取完毕之后,就会释放共享锁,这样就可以保证数据在读取时不被修改。 + - 使用方式:`SELECT ... LOCK IN SHARE MODE;` + +> 为什么要引入读写锁机制? + +实际上,读写锁是一种通用的锁机制,并非 Mysql 的专利。在很多软件领域,都存在读写锁机制。 + +因为读操作本身是线程安全的,而一般业务往往又是读多写少的情况。因此,如果对读操作进行互斥,是不必要的,并且会大大降低并发访问效率。正式为了应对这种问题,产生了读写锁机制。 + +读写锁的特点是:**读读不互斥**、**读写互斥**、**写写互斥**。简言之:**只要存在写锁,其他事务就不能做任何操作**。 + +> 注:InnoDB 下的行锁、间隙锁、next-key 锁统统属于独享锁。 + +#### 悲观锁和乐观锁 + +基于加锁方式分类,Mysql 可以分为悲观锁和乐观锁。 + +- **悲观锁** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作 + - 在查询完数据的时候就把事务锁起来,直到提交事务(`COMMIT`) + - 实现方式:**使用数据库中的锁机制**。 +- **乐观锁** - 假设最好的情况——每次访问数据时,都假设数据不会被其他线程修改,不必加锁。只在更新的时候,判断一下在此期间是否有其他线程更新该数据。 + - 实现方式:**更新数据时,先使用版本号机制或 CAS 算法检查数据是否被修改**。 + +为什么要引入乐观锁? + +乐观锁也是一种通用的锁机制,在很多软件领域,都存在乐观锁机制。 + +**锁,意味着互斥,意味着阻塞。在高并发场景下,锁越多,阻塞越多,势必会拉低并发性能**。那么,为了提高并发度,能不能尽量不加锁呢? + +乐观锁,顾名思义,就是假设最好的情况——每次访问数据时,都假设数据不会被其他线程修改,不必加锁。虽然不加锁,但不意味着什么都不做,而是在更新的时候,判断一下在此期间是否有其他线程更新该数据。乐观锁最常见的实现方式,是使用版本号机制或 CAS 算法(Compare And Swap)去实现。 + +乐观锁的**优点**是:减少锁竞争,提高并发度。 + +乐观锁的**缺点**是: + +- **存在 ABA 问题**。所谓的 ABA 问题是指在并发编程中,如果一个变量初次读取的时候是 A 值,它的值被改成了 B,然后又其他线程把 B 值改成了 A,而另一个早期线程在对比值时会误以为此值没有发生改变,但其实已经发生变化了 +- 如果乐观锁所检查的数据存在大量锁竞争,会由于**不断循环重试,产生大量的 CPU 开销**。 + +#### 全局锁、表级锁、行级锁 + +前文提到了,**锁,意味着互斥,意味着阻塞。在高并发场景下,锁越多,阻塞越多,势必会拉低并发性能**。在不得不加锁的情况下,显然,加锁的范围越小,锁竞争的发生频率就越小,系统的并发程度就越高。但是,加锁也需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态)都会增加系统开销,**锁粒度越小,系统的锁操作开销就越大**。因此,在选择锁粒度时,也需要在锁开销和并发程度之间做一个权衡。 + +根据加锁的范围,MySQL 的锁大致可以分为: + +- **全局锁** - **“全局锁”会锁定整个数据库**。 +- **表级锁(table lock)** - **“表级锁”锁定整张表**。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。表级锁有: + - **表锁** - 表锁就是对数据表进行锁定,锁定粒度很大,同时发生锁冲突的概率也会较高,数据访问的并发度低。 + - **元数据锁(MDL)** - MDL 不需要显式使用,在访问一个表的时候会被自动加上。 + - 增删改查,加读锁 + - 结构变更,加写锁 + - **意向锁(Intention Lock)** + - **自增锁(AUTO-INC)** +- **行级锁(row lock)** - **“行级锁”锁定指定的行记录**。这样其它线程还是可以对同一个表中的其它行记录进行操作。行级锁有: + - **记录锁(Record Lock)** + - **间隙锁(Gap Lock)** + - **临键锁(Next-Key Lock)** + - **插入意向锁** + +以上各种加锁粒度,在不同存储引擎中的支持情况并不相同。如:InnoDB 支持全局锁、表级锁、行级锁;而 MyISAM 只支持全局锁、表级锁。 + +每个层级的锁数量是有限制的,因为锁会占用内存空间,锁空间的大小是有限的。当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如 InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。 + +### 死锁是如何产生的? + +**“死锁”是指两个或多个事务竞争同一资源,并请求锁定对方占用的资源,从而导致恶性循环的现象**。 + +产生死锁的场景: + +- 当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。 +- 多个事务同时锁定同一个资源时,也会产生死锁。 + +### 如何避免死锁? + +死锁的四个必要条件:**互斥、占有且等待、不可强占用、循环等待**。只要系统发生死锁,这些条件必然成立,但是只要破坏任意一个条件就死锁就不会成立。由此可知,要想避免死锁,就要从这几个必要条件上去着手: + +- 更新表时,**尽量使用主键更新**,减少冲突; +- **避免长事务**,尽量将长事务拆解,可以降低与其它事务发生冲突的概率; +- **设置合理的锁等待超时参数**,我们可以通过 `innodb_lock_wait_timeout` 设置合理的等待超时阈值,特别是在一些高并发的业务中,我们可以尽量将该值设置得小一些,避免大量事务等待,占用系统资源,造成严重的性能开销。 +- 在编程中**尽量按照固定的顺序来处理数据库记录**,假设有两个更新操作,分别更新两条相同的记录,但更新顺序不一样,有可能导致死锁; +- 在允许幻读和不可重复读的情况下,尽量使用读已提交事务隔离级别,可以避免 Gap Lock 导致的死锁问题; +- 还可以使用其它的方式来代替数据库实现幂等性校验。例如,使用 Redis 以及 ZooKeeper 来实现,运行效率比数据库更佳。 + +### 如何解决死锁? + +当出现死锁以后,有两种策略: + +- **设置事务等待锁的超时时间**。这个超时时间可以通过参数 `innodb_lock_wait_timeout` 来设置。 +- **开启死锁检测**,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 `innodb_deadlock_detect` 设置为 `on`,表示开启这个逻辑。 + +在 InnoDB 中,`innodb_lock_wait_timeout` 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。但是,我直接把这个时间设置成一个很小的值,比如 1s,也是不可取的。当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。 + +所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 `innodb_deadlock_detect` 的默认值本身就是 on。为了解决死锁问题,不同数据库实现了各自的死锁检测和超时机制。InnoDB 的处理策略是:**将持有最少行级排它锁的事务进行回滚**。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。因此,死锁检测可能会耗费大量的 CPU。 + +## 优化 + +### 如何发现慢 SQL? + +慢 SQL 的监控主要通过两个途径: + +- **慢查询日志**:开启 MySQL 的慢查询日志,再通过一些工具比如 mysqldumpslow 去分析对应的慢查询日志,当然现在一般的云厂商都提供了可视化的平台。 +- **服务监控**:可以在业务的基建中加入对慢 SQL 的监控,常见的方案有字节码插桩、连接池扩展、ORM 框架过程,对服务运行中的慢 SQL 进行监控和告警。 + +### 什么是执行计划? + +### 如何分析执行计划? + +### 如何优化 SQL + +#### 避免不必要的列 + +这个是老生常谈,但还是经常会出的情况,SQL 查询的时候,应该只查询需要的列,而不要包含额外的列,像`slect *` 这种写法应该尽量避免。 + +#### 分页优化 + +在数据量比较大,分页比较深的情况下,需要考虑分页的优化。 + +例如: + +```text +select * from table where type = 2 and level = 9 order by id asc limit 190289,10; +``` + +优化方案: + +- **延迟关联** + +先通过 where 条件提取出主键,在将该表与原数据表关联,通过主键 id 提取数据行,而不是通过原来的二级索引提取数据行 + +例如: + +```text +select a.* from table a, + (select id from table where type = 2 and level = 9 order by id asc limit 190289,10 ) b + where a.id = b.id +``` + +- **书签方式** + +书签方式就是找到 limit 第一个参数对应的主键值,根据这个主键值再去过滤并 limit + +例如: + +```text + select * from table where id > + (select * from table where type = 2 and level = 9 order by id asc limit 190 +``` + +#### 索引优化 + +合理地设计和使用索引,是优化慢 SQL 的利器。 + +**利用覆盖索引** + +InnoDB 使用非主键索引查询数据时会回表,但是如果索引的叶节点中已经包含要查询的字段,那它没有必要再回表查询了,这就叫覆盖索引 + +例如对于如下查询: + +```text +select name from test where city='上海' +``` + +我们将被查询的字段建立到联合索引中,这样查询结果就可以直接从索引中获取 + +```text +alter table test add index idx_city_name (city, name); +``` + +**低版本避免使用 or 查询** + +在 MySQL 5.0 之前的版本要尽量避免使用 or 查询,可以使用 union 或者子查询来替代,因为早期的 MySQL 版本使用 or 查询可能会导致索引失效,高版本引入了索引合并,解决了这个问题。 + +**避免使用 != 或者 <> 操作符** + +SQL 中,不等于操作符会导致查询引擎放弃查询索引,引起全表扫描,即使比较的字段上有索引 + +解决方法:通过把不等于操作符改成 or,可以使用索引,避免全表扫描 + +例如,把`column<>’aaa’,改成column>’aaa’ or column<’aaa’`,就可以使用索引了 + +**适当使用前缀索引** + +适当地使用前缀所云,可以降低索引的空间占用,提高索引的查询效率。 + +比如,邮箱的后缀都是固定的“`@xxx.com`”,那么类似这种后面几位为固定值的字段就非常适合定义为前缀索引 + +```text +alter table test add index index2(email(6)); +``` + +PS:需要注意的是,前缀索引也存在缺点,MySQL 无法利用前缀索引做 order by 和 group by 操作,也无法作为覆盖索引 + +**避免列上函数运算** + +要避免在列字段上进行算术运算或其他表达式运算,否则可能会导致存储引擎无法正确使用索引,从而影响了查询的效率 + +```text +select * from test where id + 1 = 50; +select * from test where month(updateTime) = 7; +``` + +**正确使用联合索引** + +使用联合索引的时候,注意最左匹配原则。 + +#### JOIN 优化 + +**优化子查询** + +尽量使用 Join 语句来替代子查询,因为子查询是嵌套查询,而嵌套查询会新创建一张临时表,而临时表的创建与销毁会占用一定的系统资源以及花费一定的时间,同时对于返回结果集比较大的子查询,其对查询性能的影响更大 + +**小表驱动大表** + +关联查询的时候要拿小表去驱动大表,因为关联的时候,MySQL 内部会遍历驱动表,再去连接被驱动表。 + +比如 left join,左表就是驱动表,A 表小于 B 表,建立连接的次数就少,查询速度就被加快了。 + +```text + select name from A left join B ; +``` + +**适当增加冗余字段** + +增加冗余字段可以减少大量的连表查询,因为多张表的连表查询性能很低,所有可以适当的增加冗余字段,以减少多张表的关联查询,这是以空间换时间的优化策略 + +**避免使用 JOIN 关联太多的表** + +《阿里巴巴 Java 开发手册》规定不要 join 超过三张表,第一 join 太多降低查询的速度,第二 join 的 buffer 会占用更多的内存。 + +如果不可避免要 join 多张表,可以考虑使用数据异构的方式异构到 ES 中查询。 + +#### 排序优化 + +**利用索引扫描做排序** + +MySQL 有两种方式生成有序结果:其一是对结果集进行排序的操作,其二是按照索引顺序扫描得出的结果自然是有序的 + +但是如果索引不能覆盖查询所需列,就不得不每扫描一条记录回表查询一次,这个读操作是随机 IO,通常会比顺序全表扫描还慢 + +因此,在设计索引时,尽可能使用同一个索引既满足排序又用于查找行 + +例如: + +```text +--建立索引(date,staff_id,customer_id) +select staff_id, customer_id from test where date = '2010-01-01' order by staff_id,customer_id; +``` + +只有当索引的列顺序和 ORDER BY 子句的顺序完全一致,并且所有列的排序方向都一样时,才能够使用索引来对结果做排序 + +#### UNION 优化 + +**条件下推** + +MySQL 处理 union 的策略是先创建临时表,然后将各个查询结果填充到临时表中最后再来做查询,很多优化策略在 union 查询中都会失效,因为它无法利用索引 + +最好手工将 where、limit 等子句下推到 union 的各个子查询中,以便优化器可以充分利用这些条件进行优化 + +此外,除非确实需要服务器去重,一定要使用 union all,如果不加 all 关键字,MySQL 会给临时表加上 distinct 选项,这会导致对整个临时表做唯一性检查,代价很高。 + +### 哪种 COUNT 性能最好? + +> 先说结论:按照效率排序的话,`COUNT(字段)` < `COUNT(主键 id)` < `COUNT(1)` ≈ `COUNT(*)`。**推荐采用 `COUNT(*)`** 。 + +- **对于 `COUNT(主键 id)` 来说**,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。 + +- **对于 `COUNT(1)` 来说**,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。 + +- 单看这两个用法的差别的话,你能对比出来,`COUNT(1)` 执行得要比 `COUNT(主键 id)` 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。 + +- **对于 `COUNT(字段)` 来说**: + - 如果这个“字段”是定义为 `not null` 的话,一行行地从记录里面读出这个字段,判断不能为 `null`,按行累加; + - 如果这个“字段”定义允许为 `null`,那么执行的时候,判断到有可能是 `null`,还要把值取出来再判断一下,不是 `null` 才累加。 + - 也就是前面的第一条原则,server 层要什么字段,InnoDB 就返回什么字段。 + +**但是 `COUNT(*)` 是例外**,并不会把全部字段取出来,而是专门做了优化,不取值。`COUNT(*)` 肯定不是 `null`,按行累加。 + +不同的 MySQL 引擎中,`COUNT(*)` 有不同的实现方式: + +- MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 `COUNT(*)` 的时候会直接返回这个数,效率很高; +- 而 InnoDB 引擎就麻烦了,它执行 `COUNT(*)` 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。 + +> 为什么 InnoDB 不跟 MyISAM 一样,也维护一个计数器? + +因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。 + +InnoDB 是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。所以,普通索引树比主键索引树小很多。对于 `COUNT(*)` 这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。因此,MySQL 优化器会找到最小的那棵树来遍历。 + +- MyISAM 表虽然 `COUNT(*)` 很快,但是不支持事务; +- `show table status` 命令虽然返回很快,但是不准确; +- InnoDB 表直接 `COUNT(*)` 会遍历全表,虽然结果准确,但会导致性能问题。 + +> 如何优化查询计数? + +可以使用 Redis 保存计数,但存在丢失更新一集数据不一致问题。 + +可以使用数据库其他表保存计数,但要用事务进行控制,增/删数据时,同步改变计数。 + +## 参考资料 + +- [《高性能 MySQL》](https://book.douban.com/subject/23008813/) +- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) +- [图解 MySQL 介绍](https://xiaolincoding.com/mysql/) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" index cb62ac75e2..96f5d2a449 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/02.Mysql/README.md" @@ -1,4 +1,5 @@ --- +icon: logos:mysql title: Mysql 教程 date: 2020-02-10 14:27:39 categories: @@ -11,60 +12,90 @@ tags: - Mysql permalink: /pages/a5b63b/ hidden: true +index: false --- # Mysql 教程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309212108260.png) ## 📖 内容 -### [Mysql 应用指南](01.Mysql应用指南.md) +### [Mysql 架构](01.Mysql架构.md) -### [Mysql 工作流](02.MySQL工作流.md) +### [Mysql 存储引擎](02.Mysql存储引擎.md) -### [Mysql 事务](03.Mysql事务.md) +### [Mysql 索引](03.Mysql索引.md) -> 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` +### [Mysql 事务](04.Mysql事务.md) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220721072721.png) +### [Mysql 锁](05.Mysql锁.md) -### [Mysql 锁](04.Mysql锁.md) +### [Mysql 高可用](06.Mysql高可用.md) -> 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` +### [Mysql 优化](07.Mysql优化.md) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716064947.png) +### [Mysql 运维](20.Mysql运维.md) -### [Mysql 索引](05.Mysql索引.md) - -> 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200715172009.png) - -### [Mysql 性能优化](06.Mysql性能优化.md) - -### [Mysql 运维](20.Mysql运维.md) 🔨 - -### [Mysql 配置](21.Mysql配置.md) 🔨 - -### [Mysql 常见问题](99.Mysql常见问题) +### [Mysql 面试](99.Mysql面试.md) ## 📚 资料 - **官方** - [Mysql 官网](https://www.mysql.com/) - [Mysql 官方文档](https://dev.mysql.com/doc/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) + - **官方 PPT** + - [How to Analyze and Tune MySQL Queries for Better Performance](https://www.mysql.com/cn/why-mysql/presentations/tune-mysql-queries-performance/) + - [MySQL Performance Tuning 101](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning101/) + - [MySQL Performance Schema & Sys Schema](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-sys-schema/) + - [MySQL Performance: Demystified Tuning & Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning-best-practices/) + - [MySQL Security Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-security-best-practices/) + - [MySQL Cluster Deployment Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-cluster-deployment-best-practices/) + - [MySQL High Availability with InnoDB Cluster](https://www.mysql.com/cn/why-mysql/presentations/mysql-high-availability-innodb-cluster/) - **书籍** - - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 - - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - 适合入门者 + - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册【进阶】 + - [《MySQL 技术内幕:InnoDB 存储引擎》](https://book.douban.com/subject/24708143/) + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - Mysql 的基本概念和语法【入门】 - **教程** - - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) - - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) + - [SQL 必知必会](https://time.geekbang.org/column/intro/192) - 极客 SQL 教程 + - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) - 极客 Mysql 教程 + - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.md) - 入门级 SQL 教程 - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **文章** + - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.md) + - [Some study on database storage internals](https://medium.com/@kousiknath/data-structures-database-storage-internals-1f5ed3619d43) + - [Sharding Pinterest: How we scaled our MySQL fleet](https://medium.com/@Pinterest_Engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f) + - [Guide to MySQL High Availability](https://www.mysql.com/cn/why-mysql/white-papers/mysql-guide-to-high-availability-solutions/) + - [Choosing MySQL High Availability Solutions](https://dzone.com/articles/choosing-mysql-high-availability-solutions) + - [High availability with MariaDB TX: The definitive guide](https://mariadb.com/sites/default/files/content/Whitepaper_High_availability_with_MariaDB-TX.pdf) + - [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases) + - [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx) + - Mysql 相关经验 + - [20+ 条 MySQL 性能优化的最佳经验](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) + - [Booking.com: Evolution of MySQL System Design](https://www.percona.com/live/mysql-conference-2015/sessions/bookingcom-evolution-mysql-system-design) ,Booking.com 的 MySQL 数据库使用的演化,其中有很多不错的经验分享,我相信也是很多公司会遇到的的问题。 + - [Tracking the Money - Scaling Financial Reporting at Airbnb](https://medium.com/airbnb-engineering/tracking-the-money-scaling-financial-reporting-at-airbnb-6d742b80f040) ,Airbnb 的数据库扩展的经验分享。 + - [Why Uber Engineering Switched from Postgres to MySQL](https://eng.uber.com/mysql-migration/) ,无意比较两个数据库谁好谁不好,推荐这篇 Uber 的长文,主要是想让你从中学习到一些经验和技术细节,这是一篇很不错的文章。 + - Mysql 集群复制 + - [Monitoring Delayed Replication, With A Focus On MySQL](https://engineering.imvu.com/2013/01/09/monitoring-delayed-replication-with-a-focus-on-mysql/) + - [Mitigating replication lag and reducing read load with freno](https://githubengineering.com/mitigating-replication-lag-and-reducing-read-load-with-freno/) + - [Better Parallel Replication for MySQL](https://medium.com/booking-com-infrastructure/better-parallel-replication-for-mysql-14e2d7857813) + - [Evaluating MySQL Parallel Replication Part 2: Slave Group Commit](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-2-slave-group-commit-459026a141d2) + - [Evaluating MySQL Parallel Replication Part 3: Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-3-benchmarks-in-production-db5811058d74) + - [Evaluating MySQL Parallel Replication Part 4: More Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-more-benchmarks-in-production-49ee255043ab) + - [Evaluating MySQL Parallel Replication Part 4, Annex: Under the Hood](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-annex-under-the-hood-eb456cf8b2fb) + - Mysql 数据分区 + - [StackOverflow: MySQL sharding approaches?](https://stackoverflow.com/questions/5541421/mysql-sharding-approaches) + - [Why you don’t want to shard](https://www.percona.com/blog/2009/08/06/why-you-dont-want-to-shard/) + - [How to Scale Big Data Applications](https://www.percona.com/sites/default/files/presentations/How to Scale Big Data Applications.pdf) + - [MySQL Sharding with ProxySQL](https://www.percona.com/blog/2016/08/30/mysql-sharding-with-proxysql/) + - 各公司的 Mysql 数据分区经验分享 + - [MailChimp: Using Shards to Accommodate Millions of Users](https://devs.mailchimp.com/blog/using-shards-to-accommodate-millions-of-users/) + - [Uber: Code Migration in Production: Rewriting the Sharding Layer of Uber’s Schemaless Datastore](https://eng.uber.com/schemaless-rewrite/) + - [Sharding & IDs at Instagram](https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c) + - [Airbnb: How We Partitioned Airbnb’s Main Database in Two Weeks](https://medium.com/airbnb-engineering/how-we-partitioned-airbnb-s-main-database-in-two-weeks-55f7e006ff21) - **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - MySQL 的资源列表 ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" index bf9ac52586..f2dd84c7b8 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/01.PostgreSQL.md" @@ -1,6 +1,7 @@ --- title: PostgreSQL 应用指南 date: 2019-08-22 09:02:39 +order: 01 categories: - 数据库 - 关系型数据库 @@ -18,7 +19,7 @@ permalink: /pages/52609d/ > > 关键词:Database, RDBM, psql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20180920181010182614.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20180920181010182614.png) ## 安装 @@ -28,7 +29,7 @@ permalink: /pages/52609d/ 官方下载页面要求用户选择相应版本,然后动态的给出安装提示,如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20180920181010174348.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20180920181010174348.png) 前 3 步要求用户选择,后 4 步是根据选择动态提示的安装步骤 @@ -194,6 +195,6 @@ psql -h 127.0.0.1 -U user_name db_name < dump.sql - https://www.postgresql.org/download/ - http://www.ruanyifeng.com/blog/2013/12/getting_started_with_postgresql.html -## :door: 传送门 +## 🚪 传送 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" index eb984689c8..a020720d55 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/02.H2.md" @@ -1,6 +1,7 @@ --- title: H2 应用指南 date: 2019-08-22 09:02:39 +order: 02 categories: - 数据库 - 关系型数据库 @@ -28,11 +29,11 @@ H2 允许用户通过浏览器接口方式访问 SQL 数据库。 2. 启动方式:在 bin 目录下,双击 jar 包;执行 `java -jar h2*.jar`;执行脚本:`h2.bat` 或 `h2.sh`。 3. 在浏览器中访问:`http://localhost:8082`,应该可以看到下图中的页面: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/h2/h2-console.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/h2/h2-console.png) 点击 **Connect** ,可以进入操作界面: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/h2/h2-console-02.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/h2/h2-console-02.png) 操作界面十分简单,不一一细说。 @@ -469,6 +470,6 @@ H2 可以通过 CreateCluster 工具创建集群,示例步骤如下(在在 - [h2database 官网](http://www.h2database.com/html/main.html) - [Java 嵌入式数据库 H2 学习总结(一)——H2 数据库入门](https://www.cnblogs.com/xdp-gacl/p/4171024.html) -## :door: 传送门 +## 🚪 传送 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" index bcc3b9c14d..d91efa0c22 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/03.Sqlite.md" @@ -1,6 +1,7 @@ --- title: sqlite date: 2019-08-22 09:02:39 +order: 03 categories: - 数据库 - 关系型数据库 @@ -245,37 +246,37 @@ sqlite>.help ### 常用命令清单 -| 命令 | 描述 | -| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| .backup ?DB? FILE | 备份 DB 数据库(默认是 "main")到 FILE 文件。 | -| .bail ON\|OFF | 发生错误后停止。默认为 OFF。 | -| .databases | 列出数据库的名称及其所依附的文件。 | -| .dump ?TABLE? | 以 SQL 文本格式转储数据库。如果指定了 TABLE 表,则只转储匹配 LIKE 模式的 TABLE 表。 | -| .echo ON\|OFF | 开启或关闭 echo 命令。 | -| .exit | 退出 SQLite 提示符。 | -| .explain ON\|OFF | 开启或关闭适合于 EXPLAIN 的输出模式。如果没有带参数,则为 EXPLAIN on,及开启 EXPLAIN。 | -| .header(s) ON\|OFF | 开启或关闭头部显示。 | -| .help | 显示消息。 | -| .import FILE TABLE | 导入来自 FILE 文件的数据到 TABLE 表中。 | -| .indices ?TABLE? | 显示所有索引的名称。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表的索引。 | -| .load FILE ?ENTRY? | 加载一个扩展库。 | -| .log FILE\|off | 开启或关闭日志。FILE 文件可以是 stderr(标准错误)/stdout(标准输出)。 | -| .mode MODE | 设置输出模式,MODE 可以是下列之一:**csv** 逗号分隔的值**column** 左对齐的列**html** HTML 的 代码**insert** TABLE 表的 SQL 插入(insert)语句**line** 每行一个值**list** 由 .separator 字符串分隔的值**tabs** 由 Tab 分隔的值**tcl** TCL 列表元素 | -| .nullvalue STRING | 在 NULL 值的地方输出 STRING 字符串。 | -| .output FILENAME | 发送输出到 FILENAME 文件。 | -| .output stdout | 发送输出到屏幕。 | -| .print STRING... | 逐字地输出 STRING 字符串。 | -| .prompt MAIN CONTINUE | 替换标准提示符。 | -| .quit | 退出 SQLite 提示符。 | -| .read FILENAME | 执行 FILENAME 文件中的 SQL。 | -| .schema ?TABLE? | 显示 CREATE 语句。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表。 | -| .separator STRING | 改变输出模式和 .import 所使用的分隔符。 | -| .show | 显示各种设置的当前值。 | -| .stats ON\|OFF | 开启或关闭统计。 | -| .tables ?PATTERN? | 列出匹配 LIKE 模式的表的名称。 | -| .timeout MS | 尝试打开锁定的表 MS 毫秒。 | -| .width NUM NUM | 为 "column" 模式设置列宽度。 | -| .timer ON\|OFF | 开启或关闭 CPU 定时器。 | +| 命令 | 描述 | +| --------------------- | ------------------------------------------------------------ | +| .backup ?DB? FILE | 备份 DB 数据库(默认是 "main")到 FILE 文件。 | +| .bail ON\|OFF | 发生错误后停止。默认为 OFF。 | +| .databases | 列出数据库的名称及其所依附的文件。 | +| .dump ?TABLE? | 以 SQL 文本格式转储数据库。如果指定了 TABLE 表,则只转储匹配 LIKE 模式的 TABLE 表。 | +| .echo ON\|OFF | 开启或关闭 echo 命令。 | +| .exit | 退出 SQLite 提示符。 | +| .explain ON\|OFF | 开启或关闭适合于 EXPLAIN 的输出模式。如果没有带参数,则为 EXPLAIN on,及开启 EXPLAIN。 | +| .header(s) ON\|OFF | 开启或关闭头部显示。 | +| .help | 显示消息。 | +| .import FILE TABLE | 导入来自 FILE 文件的数据到 TABLE 表中。 | +| .indices ?TABLE? | 显示所有索引的名称。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表的索引。 | +| .load FILE ?ENTRY? | 加载一个扩展库。 | +| .log FILE\|off | 开启或关闭日志。FILE 文件可以是 stderr(标准错误)/stdout(标准输出)。 | +| .mode MODE | 设置输出模式,MODE 可以是下列之一:
csv 逗号分隔的值
column 左对齐的列
html HTML 的 `
` 代码
insert TABLE 表的 SQL 插入(insert)语句
line 每行一个值
list 由 .separator 字符串分隔的值
tabs 由 Tab 分隔的值
tcl TCL 列表元素 | +| .nullvalue STRING | 在 NULL 值的地方输出 STRING 字符串。 | +| .output FILENAME | 发送输出到 FILENAME 文件。 | +| .output stdout | 发送输出到屏幕。 | +| .print STRING... | 逐字地输出 STRING 字符串。 | +| .prompt MAIN CONTINUE | 替换标准提示符。 | +| .quit | 退出 SQLite 提示符。 | +| .read FILENAME | 执行 FILENAME 文件中的 SQL。 | +| .schema ?TABLE? | 显示 CREATE 语句。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表。 | +| .separator STRING | 改变输出模式和 .import 所使用的分隔符。 | +| .show | 显示各种设置的当前值。 | +| .stats ON\|OFF | 开启或关闭统计。 | +| .tables ?PATTERN? | 列出匹配 LIKE 模式的表的名称。 | +| .timeout MS | 尝试打开锁定的表 MS 毫秒。 | +| .width NUM NUM | 为 "column" 模式设置列宽度。 | +| .timer ON\|OFF | 开启或关闭 CPU 定时器。 | ### 实战 @@ -390,6 +391,6 @@ Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:"); - https://github.com/xerial/sqlite-jdbc - http://www.runoob.com/sqlite/sqlite-java.html -## :door: 传送门 +## 🚪 传送 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" index cc289d7bfd..17b2e47230 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/99.\345\205\266\344\273\226/README.md" @@ -10,6 +10,7 @@ tags: - 关系型数据库 permalink: /pages/ca9888/ hidden: true +index: false --- # 关系型数据库其他知识 @@ -24,4 +25,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" index 8c1fea13eb..9a0162063e 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/03.\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223/README.md" @@ -9,6 +9,7 @@ tags: - 关系型数据库 permalink: /pages/bb43eb/ hidden: true +index: false --- # 关系型数据库 @@ -17,25 +18,21 @@ hidden: true ### 关系型数据库综合 -- [关系型数据库面试总结](01.综合/01.关系型数据库面试.md) 💯 -- [SQL 语法基础特性](01.综合/02.SQL语法基础特性.md) -- [SQL 语法高级特性](01.综合/03.SQL语法高级特性.md) +- [SQL 语法速成](01.综合/02.SQL语法.md) - [扩展 SQL](01.综合/03.扩展SQL.md) - [SQL Cheat Sheet](01.综合/99.SqlCheatSheet.md) ### Mysql -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) - -- [Mysql 应用指南](02.Mysql/01.Mysql应用指南.md) ⚡ -- [Mysql 工作流](02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` -- [Mysql 事务](02.Mysql/03.Mysql事务.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` -- [Mysql 锁](02.Mysql/04.Mysql锁.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` -- [Mysql 索引](02.Mysql/05.Mysql索引.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` -- [Mysql 性能优化](02.Mysql/06.Mysql性能优化.md) -- [Mysql 运维](02.Mysql/20.Mysql运维.md) 🔨 -- [Mysql 配置](02.Mysql/21.Mysql配置.md) 🔨 -- [Mysql 问题](02.Mysql/99.Mysql常见问题.md) +- [Mysql 架构](02.Mysql/01.Mysql架构.md) +- [Mysql 存储引擎](02.Mysql/02.Mysql存储引擎.md) +- [Mysql 索引](02.Mysql/03.Mysql索引.md) +- [Mysql 事务](02.Mysql/04.Mysql事务.md) +- [Mysql 锁](02.Mysql/05.Mysql锁.md) +- [Mysql 高可用](02.Mysql/06.Mysql高可用.md) +- [Mysql 优化](02.Mysql/07.Mysql优化.md) +- [Mysql 运维](02.Mysql/20.Mysql运维.md) +- [Mysql 面试](02.Mysql/99.Mysql面试.md) ### 其他 @@ -49,22 +46,63 @@ hidden: true - [《数据库的索引设计与优化》](https://book.douban.com/subject/26419771/) - [《SQL 必知必会》](https://book.douban.com/subject/35167240/) - SQL 入门经典 +- [SQL 必知必会](https://time.geekbang.org/column/intro/192) - 极客时间 SQL 入门教程 ### Mysql - **官方** - [Mysql 官网](https://www.mysql.com/) - [Mysql 官方文档](https://dev.mysql.com/doc/) - - [Mysql 官方文档之命令行客户端](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) + - **官方 PPT** + - [How to Analyze and Tune MySQL Queries for Better Performance](https://www.mysql.com/cn/why-mysql/presentations/tune-mysql-queries-performance/) + - [MySQL Performance Tuning 101](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning101/) + - [MySQL Performance Schema & Sys Schema](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-sys-schema/) + - [MySQL Performance: Demystified Tuning & Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-performance-tuning-best-practices/) + - [MySQL Security Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-security-best-practices/) + - [MySQL Cluster Deployment Best Practices](https://www.mysql.com/cn/why-mysql/presentations/mysql-cluster-deployment-best-practices/) + - [MySQL High Availability with InnoDB Cluster](https://www.mysql.com/cn/why-mysql/presentations/mysql-high-availability-innodb-cluster/) - **书籍** - - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册 - - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - MySQL 入门经典 + - [《高性能 MySQL》](https://book.douban.com/subject/23008813/) - 经典,适合 DBA 或作为开发者的参考手册【进阶】 + - [《MySQL 技术内幕:InnoDB 存储引擎》](https://book.douban.com/subject/24708143/) + - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - Mysql 的基本概念和语法【入门】 - **教程** - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) - - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) + - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.md) - 入门级 SQL 教程 - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) +- **文章** + - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.md) + - [Some study on database storage internals](https://medium.com/@kousiknath/data-structures-database-storage-internals-1f5ed3619d43) + - [Sharding Pinterest: How we scaled our MySQL fleet](https://medium.com/@Pinterest_Engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f) + - [Guide to MySQL High Availability](https://www.mysql.com/cn/why-mysql/white-papers/mysql-guide-to-high-availability-solutions/) + - [Choosing MySQL High Availability Solutions](https://dzone.com/articles/choosing-mysql-high-availability-solutions) + - [High availability with MariaDB TX: The definitive guide](https://mariadb.com/sites/default/files/content/Whitepaper_High_availability_with_MariaDB-TX.pdf) + - [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases) + - [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx) + - Mysql 相关经验 + - [20+ 条 MySQL 性能优化的最佳经验](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) + - [Booking.com: Evolution of MySQL System Design](https://www.percona.com/live/mysql-conference-2015/sessions/bookingcom-evolution-mysql-system-design) ,Booking.com 的 MySQL 数据库使用的演化,其中有很多不错的经验分享,我相信也是很多公司会遇到的的问题。 + - [Tracking the Money - Scaling Financial Reporting at Airbnb](https://medium.com/airbnb-engineering/tracking-the-money-scaling-financial-reporting-at-airbnb-6d742b80f040) ,Airbnb 的数据库扩展的经验分享。 + - [Why Uber Engineering Switched from Postgres to MySQL](https://eng.uber.com/mysql-migration/) ,无意比较两个数据库谁好谁不好,推荐这篇 Uber 的长文,主要是想让你从中学习到一些经验和技术细节,这是一篇很不错的文章。 + - Mysql 集群复制 + - [Monitoring Delayed Replication, With A Focus On MySQL](https://engineering.imvu.com/2013/01/09/monitoring-delayed-replication-with-a-focus-on-mysql/) + - [Mitigating replication lag and reducing read load with freno](https://githubengineering.com/mitigating-replication-lag-and-reducing-read-load-with-freno/) + - [Better Parallel Replication for MySQL](https://medium.com/booking-com-infrastructure/better-parallel-replication-for-mysql-14e2d7857813) + - [Evaluating MySQL Parallel Replication Part 2: Slave Group Commit](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-2-slave-group-commit-459026a141d2) + - [Evaluating MySQL Parallel Replication Part 3: Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-3-benchmarks-in-production-db5811058d74) + - [Evaluating MySQL Parallel Replication Part 4: More Benchmarks in Production](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-more-benchmarks-in-production-49ee255043ab) + - [Evaluating MySQL Parallel Replication Part 4, Annex: Under the Hood](https://medium.com/booking-com-infrastructure/evaluating-mysql-parallel-replication-part-4-annex-under-the-hood-eb456cf8b2fb) + - Mysql 数据分区 + - [StackOverflow: MySQL sharding approaches?](https://stackoverflow.com/questions/5541421/mysql-sharding-approaches) + - [Why you don’t want to shard](https://www.percona.com/blog/2009/08/06/why-you-dont-want-to-shard/) + - [How to Scale Big Data Applications](https://www.percona.com/sites/default/files/presentations/How to Scale Big Data Applications.pdf) + - [MySQL Sharding with ProxySQL](https://www.percona.com/blog/2016/08/30/mysql-sharding-with-proxysql/) + - 各公司的 Mysql 数据分区经验分享 + - [MailChimp: Using Shards to Accommodate Millions of Users](https://devs.mailchimp.com/blog/using-shards-to-accommodate-millions-of-users/) + - [Uber: Code Migration in Production: Rewriting the Sharding Layer of Uber’s Schemaless Datastore](https://eng.uber.com/schemaless-rewrite/) + - [Sharding & IDs at Instagram](https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c) + - [Airbnb: How We Partitioned Airbnb’s Main Database in Two Weeks](https://medium.com/airbnb-engineering/how-we-partitioned-airbnb-s-main-database-in-two-weeks-55f7e006ff21) - **更多资源** - - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) + - [awesome-mysql](https://github.com/jobbole/awesome-mysql-cn) - MySQL 的资源列表 ### 其他 @@ -72,4 +110,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" index 78856a4639..d7856e889f 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/01.MongoDB\345\272\224\347\224\250\346\214\207\345\215\227.md" @@ -1,6 +1,7 @@ --- title: MongoDB 应用指南 date: 2020-09-07 07:54:19 +order: 01 categories: - 数据库 - 文档数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" index 913f50f9c6..3d63b9348f 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/02.MongoDB\347\232\204CRUD\346\223\215\344\275\234.md" @@ -1,6 +1,7 @@ --- title: MongoDB 的 CRUD 操作 date: 2020-09-25 21:23:41 +order: 02 categories: - 数据库 - 文档数据库 @@ -27,7 +28,7 @@ MongoDB 提供以下操作向一个 collection 插入 document > 注:以上操作都是原子操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924112342.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924112342.svg) 插入操作的特性: @@ -76,7 +77,7 @@ db.inventory.insertMany([ MongoDB 提供 [`db.collection.find()`](https://docs.mongodb.com/manual/reference/method/db.collection.find/#db.collection.find) 方法来检索 document。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924113832.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924113832.svg) ### Update 操作 @@ -92,7 +93,7 @@ MongoDB 提供以下操作来更新 collection 中的 document - [`db.collection.updateMany(, , )`](https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/#db.collection.updateMany) - [`db.collection.replaceOne(, , )`](https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/#db.collection.replaceOne) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924114043.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924114043.svg) 【示例】插入测试数据 @@ -201,7 +202,7 @@ MongoDB 提供以下操作来删除 collection 中的 document - [`db.collection.deleteOne()`](https://docs.mongodb.com/manual/reference/method/db.collection.deleteOne/#db.collection.deleteOne):删除一条 document - [`db.collection.deleteMany()`](https://docs.mongodb.com/manual/reference/method/db.collection.deleteMany/#db.collection.deleteMany):删除多条 document -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200924120007.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200924120007.svg) 删除操作的特性: diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" index 172fb73d3c..b699f0e0ff 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/03.MongoDB\347\232\204\350\201\232\345\220\210\346\223\215\344\275\234.md" @@ -1,6 +1,7 @@ --- title: MongoDB 的聚合操作 date: 2020-09-21 21:22:57 +order: 03 categories: - 数据库 - 文档数据库 @@ -29,7 +30,7 @@ MongoDB Pipeline 由多个阶段([stages](https://docs.mongodb.com/manual/refe 同一个阶段可以在 pipeline 中出现多次,但 [`$out`](https://docs.mongodb.com/manual/reference/operator/aggregation/out/#pipe._S_out)、[`$merge`](https://docs.mongodb.com/manual/reference/operator/aggregation/merge/#pipe._S_merge),和 [`$geoNear`](https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#pipe._S_geoNear) 阶段除外。所有可用 pipeline 阶段可以参考:[Aggregation Pipeline Stages](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/#aggregation-pipeline-operator-reference)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921092725.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921092725.png) - 第一阶段:[`$match`](https://docs.mongodb.com/manual/reference/operator/aggregation/match/#pipe._S_match) 阶段按状态字段过滤 document,然后将状态等于“ A”的那些 document 传递到下一阶段。 - 第二阶段:[`$group`](https://docs.mongodb.com/manual/reference/operator/aggregation/group/#pipe._S_group) 阶段按 cust_id 字段对 document 进行分组,以计算每个唯一 cust_id 的金额总和。 @@ -239,7 +240,7 @@ Pipeline 的内存限制为 100 MB。 Map-reduce 是一种数据处理范式,用于将大量数据汇总为有用的聚合结果。为了执行 map-reduce 操作,MongoDB 提供了 [`mapReduce`](https://docs.mongodb.com/manual/reference/command/mapReduce/#dbcmd.mapReduce) 数据库命令。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921155546.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921155546.svg) 在上面的操作中,MongoDB 将 map 阶段应用于每个输入 document(即 collection 中与查询条件匹配的 document)。 map 函数分发出多个键-值对。对于具有多个值的那些键,MongoDB 应用 reduce 阶段,该阶段收集并汇总聚合的数据。然后,MongoDB 将结果存储在 collection 中。可选地,reduce 函数的输出可以通过 finalize 函数来进一步汇总聚合结果。 @@ -255,7 +256,7 @@ MongoDB 支持一下单一目的的聚合操作: 所有这些操作都汇总了单个 collection 中的 document。尽管这些操作提供了对常见聚合过程的简单访问,但是它们相比聚合 pipeline 和 map-reduce,缺少灵活性和丰富的功能性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921155935.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921155935.svg) ## SQL 和 MongoDB 聚合对比 @@ -386,7 +387,7 @@ db.orders.insertMany([ SQL 和 MongoDB 聚合方式对比: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921200556.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921200556.png) ## 参考资料 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" index 527172f5c6..781b34286a 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/04.MongoDB\344\272\213\345\212\241.md" @@ -1,6 +1,7 @@ --- title: MongoDB 事务 date: 2020-09-20 23:12:17 +order: 04 categories: - 数据库 - 文档数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" index ba54c8106e..0bade5f358 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/05.MongoDB\345\273\272\346\250\241.md" @@ -1,6 +1,7 @@ --- title: MongoDB 建模 date: 2020-09-09 20:47:14 +order: 05 categories: - 数据库 - 文档数据库 @@ -120,7 +121,7 @@ This looks very different from the tabular data structure you started with in St 嵌入式 document 通过将相关数据存储在单个 document 结构中来捕获数据之间的关系。 MongoDB document 可以将 document 结构嵌入到另一个 document 中的字段或数组中。这些非规范化的数据模型允许应用程序在单个数据库操作中检索和操纵相关数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200910193231.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200910193231.png) 对于 MongoDB 中的很多场景,非规范化数据模型都是最佳的。 @@ -132,7 +133,7 @@ This looks very different from the tabular data structure you started with in St 引用通过包含从一个 document 到另一个 document 的链接或引用来存储数据之间的关系。 应用程序可以解析这些引用以访问相关数据。 广义上讲,这些是规范化的数据模型。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200910193234.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200910193234.png) 通常,在以下场景使用引用式的数据模型: diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" index 718ae75e76..ebc92a3bff 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/06.MongoDB\345\273\272\346\250\241\347\244\272\344\276\213.md" @@ -1,6 +1,7 @@ --- title: MongoDB 建模示例 date: 2020-09-12 10:43:53 +order: 06 categories: - 数据库 - 文档数据库 @@ -370,7 +371,7 @@ review collection 存储所有的评论 ## 树形结构模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200911194846.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200911194846.svg) ### 具有父节点的树形结构模型 @@ -524,7 +525,7 @@ db.categories.insertMany([ ### 具有嵌套集的树形结构模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200911204252.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200911204252.svg) ```javascript db.categories.insertMany([ @@ -553,7 +554,7 @@ db.categories.find({ 解决方案是:列转行 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200919225901.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200919225901.png) ### 管理文档不同版本 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" index 6caabc1dc9..32dee6ab13 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/07.MongoDB\347\264\242\345\274\225.md" @@ -1,6 +1,7 @@ --- title: MongoDB 索引 date: 2020-09-21 21:22:57 +order: 07 categories: - 数据库 - 文档数据库 @@ -27,7 +28,7 @@ permalink: /pages/10c674/ 索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200921210621.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200921210621.svg) ### createIndex() 方法 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" index 63c2fe74ec..8c5341e2e9 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/08.MongoDB\345\244\215\345\210\266.md" @@ -1,6 +1,7 @@ --- title: MongoDB 复制 date: 2020-09-20 23:12:17 +order: 08 categories: - 数据库 - 文档数据库 @@ -27,15 +28,15 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 **主节点负责接收所有写操作**。副本集只能有一个主副本,能够以 [`{ w: "majority" }`](https://docs.mongodb.com/manual/reference/write-concern/#writeconcern."majority") 来确认集群中节点的写操作成功情况;尽管在某些情况下,另一个 MongoDB 实例可能会暂时认为自己也是主要的。主节点在其操作日志(即 [oplog](https://docs.mongodb.com/manual/core/replica-set-oplog/))中记录了对其数据集的所有更改。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165054.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165054.svg) **从节点复制主节点的操作日志,并将操作应用于其数据集**,以便同步主节点的数据。如果主节点不可用,则符合条件的从节点将选举新的主节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165055.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165055.svg) 在某些情况下(例如,有一个主节点和一个从节点,但由于成本限制,禁止添加另一个从节点),您可以选择将 mongod 实例作为仲裁节点添加到副本集。仲裁节点参加选举但不保存数据(即不提供数据冗余)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920165053.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920165053.svg) 仲裁节点将永远是仲裁节点。在选举期间,主节点可能会降级成为次节点,而次节点可能会升级成为主节点。 @@ -61,7 +62,7 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 当主节点与集群中的其他成员通信的时间超过配置的 `electionTimeoutMillis`(默认为 10 秒)时,符合选举要求的从节点将要求选举,并提名自己为新的主节点。集群尝试完成选举新主节点并恢复正常工作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920175429.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920175429.svg) 选举完成前,副本集无法处理写入操作。如果将副本集配置为:在主节点处于脱机状态时,在次节点上运行,则副本集可以继续提供读取查询。 @@ -79,7 +80,7 @@ MongoDB 中的副本集是一组维护相同数据集的 mongod 进程。一个 默认情况下,客户端从主节点读取数据;但是,客户端可以指定读取首选项,以将读取操作发送到从节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920204024.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920204024.svg) 异步复制到从节点意味着向从节点读取数据可能会返回与主节点不一致的数据。 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" index f8eff83568..c6b3a8c4c5 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/09.MongoDB\345\210\206\347\211\207.md" @@ -1,6 +1,7 @@ --- title: MongoDB 分片 date: 2020-09-20 23:12:17 +order: 09 categories: - 数据库 - 文档数据库 @@ -37,7 +38,7 @@ MongoDB 分片集群含以下组件: - [mongos](https://docs.mongodb.com/manual/core/sharded-cluster-query-router/):mongos 充当查询路由器,在客户端应用程序和分片集群之间提供接口。从 MongoDB 4.4 开始,mongos 可以支持 [hedged reads](https://docs.mongodb.com/manual/core/sharded-cluster-query-router/#mongos-hedged-reads) 以最大程度地减少延迟。 - [config servers](https://docs.mongodb.com/manual/core/sharded-cluster-config-servers/):提供集群元数据存储和分片数据分布的映射。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920210057.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920210057.svg) ### 分片集群的分布 @@ -49,7 +50,7 @@ MongoDB 数据库可以同时包含分片和未分片的集合的 collection。 分片和未分片的 collection: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920212159.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920212159.svg) ### 路由节点 mongos @@ -57,7 +58,7 @@ MongoDB 数据库可以同时包含分片和未分片的集合的 collection。 连接 [`mongos`](https://docs.mongodb.com/manual/reference/program/mongos/#bin.mongos) 的方式和连接 [`mongod`](https://docs.mongodb.com/manual/reference/program/mongod/#bin.mongod) 相同,例如通过 [`mongo`](https://docs.mongodb.com/manual/reference/program/mongo/#bin.mongo) shell 或 [MongoDB 驱动程序](https://docs.mongodb.com/drivers/?jump=docs)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920212157.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920212157.svg) 路由节点的作用: @@ -104,7 +105,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 > 注意:使用哈希索引解析查询时,MongoDB 会自动计算哈希值,应用程序不需要计算哈希。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920213343.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920213343.svg) 尽管分片 Key 范围可能是“接近”的,但它们的哈希值不太可能在同一 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 上。基于 Hash 的数据分发有助于更均匀的数据分布,尤其是在分片 Key 单调更改的数据集中。 @@ -114,7 +115,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 范围分片根据分片 Key 值将数据划分为多个范围。然后,根据分片 Key 值为每个 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 分配一个范围。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920213345.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920213345.svg) 值比较近似的一系列分片 Key 更有可能驻留在同一 [chunk](https://docs.mongodb.com/manual/reference/glossary/#term-chunk) 上。范围分片的效率取决于选择的分片 Key。分片 Key 考虑不周全会导致数据分布不均,这可能会削弱分片的某些优势或导致性能瓶颈。 @@ -126,7 +127,7 @@ Hash 分片策略会先计算分片 Key 字段值的哈希值;然后,根据 每个区域覆盖一个或多个分片 Key 值范围。区域覆盖的每个范围始终包括其上下边界。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200920214854.svg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200920214854.svg) 在定义要覆盖的区域的新范围时,必须使用分片 Key 中包含的字段。如果使用复合分片 Key,则范围必须包含分片 Key 的前缀。 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" index 2f5284955f..eaaf16f165 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/20.MongoDB\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: MongoDB 运维 date: 2020-09-09 20:47:14 +order: 20 categories: - 数据库 - 文档数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" index cc6f228b02..7cc53846b3 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/01.MongoDB/README.md" @@ -11,6 +11,7 @@ tags: - MongoDB permalink: /pages/b1a116/ hidden: true +index: false --- # MongoDB 教程 @@ -59,4 +60,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/README.md" new file mode 100644 index 0000000000..753b779233 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/04.\346\226\207\346\241\243\346\225\260\346\215\256\345\272\223/README.md" @@ -0,0 +1,59 @@ +--- +title: 文档数据库 +date: 2023-09-08 15:50:34 +categories: + - 数据库 + - 文档数据库 +tags: + - 数据库 + - 文档数据库 +permalink: /pages/d1dc5f/ +hidden: true +index: false +--- + +# 文档数据库 + +## 📖 内容 + +### MongoDB + +#### [MongoDB 应用指南](01.MongoDB/01.MongoDB应用指南.md) + +#### [MongoDB 的 CRUD 操作](01.MongoDB/02.MongoDB的CRUD操作.md) + +#### [MongoDB 聚合操作](01.MongoDB/03.MongoDB的聚合操作.md) + +#### [MongoDB 事务](01.MongoDB/04.MongoDB事务.md) + +#### [MongoDB 建模](01.MongoDB/05.MongoDB建模.md) + +#### [MongoDB 建模示例](01.MongoDB/06.MongoDB建模示例.md) + +#### [MongoDB 索引](01.MongoDB/07.MongoDB索引.md) + +#### [MongoDB 复制](01.MongoDB/08.MongoDB复制.md) + +#### [MongoDB 分片](01.MongoDB/09.MongoDB分片.md) + +#### [MongoDB 运维](01.MongoDB/20.MongoDB运维.md) + +## 📚 资料 + +### MongoDB 资料 + +- **官方** + - [MongoDB 官网](https://www.mongodb.com/) + - [MongoDB Github](https://github.com/mongodb/mongo) + - [MongoDB 官方免费教程](https://university.mongodb.com/) +- **教程** + - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) + - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) +- **数据** + - [mongodb-json-files](https://github.com/ozlerhakan/mongodb-json-files) +- **文章** + - [Introduction to MongoDB](https://www.slideshare.net/mdirolf/introduction-to-mongodb) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" new file mode 100644 index 0000000000..4665da76b0 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213.md" @@ -0,0 +1,1115 @@ +--- +icon: logos:redis +title: Redis 基本数据类型 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/20230901071808.png +date: 2020-06-24 10:45:38 +order: 01 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 数据类型 +permalink: /pages/ed757c/ +--- + +# Redis 基本数据类型 + +> 关键词:`String`、`Hash`、`List`、`Set`、`Zset` + +Redis 提供了多种数据类型,每种数据类型有丰富的命令支持。 + +Redis 支持的基本数据类型:STRING、HASH、LIST、SET、ZSET + +Redis 支持的高级数据类型:BitMap、HyperLogLog、GEO、Stream + +使用 Redis ,不仅要了解其数据类型的特性,还需要根据业务场景,灵活的、高效的使用其数据类型来建模。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309232155082.png) + +## String + +### String 简介 + +String 类型是键值对结构。 + +String 类型是**二进制安全**的。二进制安全是指,String 类型不仅可以保存文本数据,还可以保存任意格式的二进制数据,如:图片、音频、视频、压缩文件等。 + +默认情况下,String 类型的值最大可为 **512 MB**。 + +
+ +
+ +### String 实现 + +String 类型的底层的数据结构实现主要是 int 和 SDS(简单动态字符串)。 + +SDS 和我们认识的 C 字符串不太一样,之所以没有使用 C 语言的字符串表示,因为 SDS 相比于 C 的原生字符串: + +- **SDS 不仅可以保存文本数据,还可以保存二进制数据**。因为 `SDS` 使用 `len` 属性的值而不是空字符来判断字符串是否结束,并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在 `buf[]` 数组里的数据。所以 SDS 不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据。 +- **SDS 获取字符串长度的时间复杂度是 O(1)**。因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用 `len` 属性记录了字符串长度,所以复杂度为 `O(1)`。 +- **Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出**。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。 + +**字符串对象的编码可以是 `int` 、 `raw` 或者 `embstr`** 。 + +字符串对象保存各类型值的编码方式: + +| 值 | 编码 | +| :-------------------------------------------------------------------------------------------------------------------------- | :------------------ | +| 可以用 `long` 类型保存的整数。 | `int` | +| 可以用 `long double` 类型保存的浮点数。 | `embstr` 或者 `raw` | +| 字符串值, 或者因为长度太大而没办法用 `long` 类型表示的整数, 又或者因为长度太大而没办法用 `long double` 类型表示的浮点数。 | `embstr` 或者 `raw` | + +如果一个字符串对象保存的是整数值, 并且这个整数值可以用 `long` 类型来表示, 那么字符串对象会将整数值保存在字符串对象结构的 `ptr` 属性里面(将 `void*` 转换成 `long` ), 并将字符串对象的编码设置为 `int` 。 + +【示例】 + +```shell +> SET number 10086 +OK + +> OBJECT ENCODING number +"int" +``` + +如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度大于 `39` 字节, 那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值, 并将对象的编码设置为 `raw` 。 + +```c +> SET story "Long, long, long ago there lived a king ..." +OK + +> STRLEN story +(integer) 43 + +> OBJECT ENCODING story +"raw" +``` + +如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度小于等于 `39` 字节, 那么字符串对象将使用 `embstr` 编码的方式来保存这个字符串值。`embstr` 编码是专门用于保存短字符串的一种优化编码方式。 + +【示例】 + +```c +> SET msg "hello" +OK + +> OBJECT ENCODING msg +"embstr" +``` + +### String 命令 + +| 命令 | 说明 | +| -------- | ----------------------------------- | +| `SET` | 存储一个字符串值 | +| `SETNX` | 仅当键不存在时,才存储字符串值 | +| `GET` | 获取指定 key 的值 | +| `MGET` | 获取一个或多个指定 key 的值 | +| `INCRBY` | 将 key 中储存的数字加上指定的增量值 | +| `DECRBY` | 将 key 中储存的数字减去指定的减量值 | + +> 更多命令请参考:[Redis String 类型官方命令文档](https://redis.io/commands#string) + +【示例】SET、GET、DEL 操作 + +```shell +# 将 key(name) 的 value 保存为 dunwu +> set name dunwu +OK +# 获取 key(name) 的 value +> get name +"dunwu" +# 将 key(name) 的 value 保存为 unknown(覆盖原 value) +> set name unknown +OK +> get name +"unknown" +# 检查 key(name) 是否存在 +> exists name +(integer) 1 +# 删除 key(name) +> del name +(integer) 1 +> exists name +(integer) 0 +> get name +(nil) +``` + +【示例】SETNX 操作 + +```shell +# 检查 key(lock) 是否存在 +> exists lock +(integer) 0 +# 将 key(lock) 的 value 保存为 1,保存成功 +> setnx lock 1 +(integer) 1 +# 将 key(lock) 的 value 保存为 2,由于 key 已存在,保存失败 +> setnx lock 2 +(integer) 0 +# 获取 key(lock) 的 value +> get lock +"1" +``` + +【示例】MSET、MGET 操作 + +```shell +# 批量设置 one、two、three 这 3 个 key +> mset one 1 tow 2 three 3 +OK +# 批量获取 one、two、three 3 个 key 的 value +> mget one tow three +1) "1" +2) "2" +3) "3" +``` + +【示例】INCR、DECR、INCRBY、DECRBY 操作 + +```shell +# 将 key(counter) 的 value 保存为 0 +> set counter 0 +OK +# 将 key(counter) 的 value 加 1 +> incr counter +(integer) 1 +# 将 key(counter) 的 value 加 9 +> incrby counter 9 +(integer) 10 +# 将 key(counter) 的 value 减 1 +> decr counter +(integer) 9 +# 将 key(counter) 的 value 减 9 +> decrby counter 9 +(integer) 0 +``` + +### String 应用 + +**适用场景:缓存、计数器、共享 Session** + +#### 缓存对象 + +使用 String 来缓存对象有两种方式: + +(1)缓存对象的 JSON 值 + +```shell +> set user:1 {"name":"dunwu","sex":"man"} +``` + +(2)将 key 分离为 user:ID:属性的形式,采用 MSET 存储,用 MGET 获取各属性值 + +```shell +> mset user:1:name dunwu user:1:sex man +OK +> mget user:1:name user:1:sex +1) "dunwu" +2) "man" +``` + +#### 计数器 + +【需求场景】 + +统计网站某内容的点击量、收藏量、点赞数等等。 + +【解决方案】 + +> 使用 Redis 的 String 类型存储一个计数器。 + +维护计数器的常见操作如下: + +- 增加统计值 - 使用 `INCR`、`DECR` 命令 +- 减少统计值 - 使用 `INCRBY`、`DECRBY` 操作 + +【示例代码】 + +```shell +# 初始化 ID 为 1024 的博文访问量为 0 +> set blog:view:1024 0 +OK +# ID 为 1024 的博文访问量加 1 +> incr blog:view:1024 +(integer) 1 +# ID 为 1024 的博文访问量加 1 +> incr blog:view:1024 +(integer) 2 +# 查看 ID 为 1024 的博文访问量 +> get blog:view:1024 +"2" +``` + +#### 分布式锁 + +(1)申请锁 + +SET 命令有个 NX 参数可以实现“key 不存在才插入”,可以用它来实现分布式锁: + +- 如果 key 不存在,则显示插入成功,可以用来表示加锁成功; +- 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。 + +一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下: + +```shell +SET key value NX PX 30000 +``` + +- key - 就是分布式锁的关键字; +- value - 是客户端生成的唯一的标识; +- NX - 表示只有 `key` 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 `nil`) +- PX 30000 - 表示:30s 后,key 会被删除(这意味着锁被释放了)。设置过期时间,是为了防止出现各种意外,导致锁始终无法释放的情况。 + +(2)释放锁 + +释放锁就是删除 key ,但是一般可以用 `lua` 脚本删除,判断 value 一样才删除,这是为了保证释放锁操作和申请所操作是同一个客户端。由于涉及两个操作,为了保证原子性,可以使用 lua 脚本来实现,因为 Redis 执行 Lua 脚本时,是以原子性方式执行的。 + +```Lua +-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。 +if redis.call("get",KEYS[1]) == ARGV[1] then + return redis.call("del",KEYS[1]) +else + return 0 +end +``` + +#### 共享 Session 信息 + +在分布式场景下,一个用户的 Session 如果只存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器上,该服务器没有用户的 Session,就可能导致用户需要重新进行登录等操作。 + +分布式 Session 的几种实现策略: + +1. 粘性 session +2. 应用服务器间的 session 复制共享 +3. 基于缓存的 session 共享 ✅ + +基于缓存的 session 共享实现 + +> **使用一个单独的存储服务器存储 Session 数据**,可以存在 MySQL 数据库上,也可以存在 Redis 或者 Memcached 这种内存型数据库。 +> +> 缺点:需要去实现存取 Session 的代码。 + +
+ +
+## Hash + +### Hash 简介 + +
+ +
+ +Hash 是一个键值对(key - value)集合,其中 value 的形式如: `value=[{field1,value1},...{fieldN,valueN}]`。Hash 特别适合用于存储对象。 + +### Hash 实现 + +哈希对象的编码可以是 `ziplist` 或者 `hashtable` 。 + +`ziplist` 编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时, 程序会先将保存了键的压缩列表节点推入到压缩列表表尾, 然后再将保存了值的压缩列表节点推入到压缩列表表尾。 + +`hashtable` 编码的哈希对象使用字典作为底层实现, 哈希对象中的每个键值对都使用一个字典键值对来保存。 + +当哈希对象同时满足以下两个条件时, 使用 `ziplist` 编码;否则,使用 `hashtable` 编码。 + +1. 哈希对象保存的所有键值对的键和值的字符串长度都小于 `64` 字节(可由 `hash-max-ziplist-value` 配置); +2. 哈希对象保存的键值对数量小于 `512` 个(可由 `hash-max-ziplist-entries` 配置); + +> 注意:这两个条件的上限值是可以修改的, 具体请看配置文件中关于 `hash-max-ziplist-value` 选项和 `hash-max-ziplist-entries` 选项的说明。 + +### Hash 命令 + +| 命令 | 行为 | +| --------- | -------------------------- | +| `HSET` | 将指定字段的值设为 value | +| `HGET` | 获取指定字段的值 | +| `HGETALL` | 获取所有键值对 | +| `HMSET` | 设置多个键值对 | +| `HMGET` | 获取所有指定字段的值 | +| `HDEL` | 删除指定字段 | +| `HINCRBY` | 为指定字段的整数值加上增量 | +| `HKEYS` | 获取所有字段 | + +> 更多命令请参考:[Redis Hash 类型官方命令文档](https://redis.io/commands#hash) + +```shell +# 存储一个哈希表key的键值 +HSET key field value +# 获取哈希表key对应的field键值 +HGET key field + +# 在一个哈希表key中存储多个键值对 +HMSET key field value [field value...] +# 批量获取哈希表key中多个field键值 +HMGET key field [field ...] +# 删除哈希表key中的field键值 +HDEL key field [field ...] + +# 返回哈希表key中field的数量 +HLEN key +# 返回哈希表key中所有的键值 +HGETALL key + +# 为哈希表key中field键的值加上增量n +HINCRBY key field n +``` + +### Hash 应用 + +> **Hash 类型适用于存储结构化数据**。 + +#### 缓存对象 + +Hash 类型的(key,field,value)的结构与对象的(对象 id,属性,值)的结构相似,也可以用来存储对象。 + +我们以用户信息为例,它在关系型数据库中的结构是这样的: + +我们可以使用如下命令,将用户对象的信息存储到 Hash 类型: + +```shell +# 存储一个哈希表uid:1的键值 +> HMSET uid:1 name Tom age 15 +2 +# 存储一个哈希表uid:2的键值 +> HMSET uid:2 name Jerry age 13 +2 +# 获取哈希表用户id为1中所有的键值 +> HGETALL uid:1 +1) "name" +2) "Tom" +3) "age" +4) "15" +``` + +Redis Hash 存储其结构如下图: + +在介绍 String 类型的应用场景时有所介绍,String + Json 也是存储对象的一种方式,那么存储对象时,到底用 String + json 还是用 Hash 呢? + +一般对象用 String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储。 + +#### 购物车 + +【需求场景】 + +用户浏览电商平台,添加商品到购物车,并支持查看购物车。需要考虑未登录的情况。 + +【解决方案】 + +> 可以使用 HASH 类型来实现购物车功能。 +> +> 以用户 session 为 key,存储了商品 ID 和商品数量的映射。其中,商品 id 为 field,商品数量为 value。 +> +> 为什么不使用用户 ID? +> +> 因为很多场景下需要支持用户在免登陆的情况下使用购物车的,因为未登录,所以无法知道用户的用户 ID,这种情况下使用用户 session 更合适。并且由于绑定的是 session,可以在清空 session 时,顺便清空购物车缓存,更加方便。 + +维护购物车的常见操作如下: + +- 添加商品 - `HSET cart:{session} {商品id} 1` +- 添加数量 - `HINCRBY cart:{session} {商品id} 1` +- 商品总数 - `HLEN cart:{session}` +- 删除商品 - `HDEL cart:{session} {商品id}` +- 获取购物车所有商品 - `HGETALL cart:{session}` + +当前仅仅是将商品 ID 存储到了 Redis 中,在回显商品具体信息的时候,还需要拿着商品 id 查询一次数据库,获取完整的商品的信息。 + +## List + +Redis 中的 List 类型就是有序列表。 + +### List 简介 + +
+ +
+ +List 列表是简单的字符串列表,**按照插入顺序排序**,可以从头部或尾部向 List 列表添加元素。 + +列表的最大长度为 `2^32 - 1`,也即每个列表支持超过 `40 亿`个元素。 + +### List 实现 + +列表对象的编码可以是 `ziplist` 或者 `linkedlist` 。 + +`ziplist` 编码的列表对象使用压缩列表作为底层实现, 每个压缩列表节点(entry)保存了一个列表元素。 + +![](http://redisbook.com/_images/graphviz-a8d31075b4c0537f4eb6d84aaba1df928c67c953.png) + +`inkedlist` 编码的列表对象使用双链表作为底层实现。 + +![](http://redisbook.com/_images/graphviz-84c0d231f30c740a431407c7aaf3851b96399590.png) + +当列表对象可以同时满足以下两个条件时, 列表对象使用 `ziplist` 编码;否则,使用 `linkedlist` 编码 + +1. 列表对象保存的所有字符串元素的长度都小于 `64` 字节; +2. 列表对象保存的元素数量小于 `512` 个; + +> 注意 +> +> 以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 `list-max-ziplist-value` 选项和 `list-max-ziplist-entries` 选项的说明。 + +### List 命令 + +| 命令 | 行为 | +| -------- | ------------------------------------------ | +| `LPUSH` | 将给定值推入列表的右端。 | +| `RPUSH` | 将给定值推入列表的右端。 | +| `LPOP` | 从列表的左端弹出一个值,并返回被弹出的值。 | +| `RPOP` | 从列表的右端弹出一个值,并返回被弹出的值。 | +| `LRANGE` | 获取列表在给定范围上的所有值。 | +| `LINDEX` | 获取列表在给定位置上的单个元素。 | +| `LREM` | 从列表的左端弹出一个值,并返回被弹出的值。 | +| `LTRIM` | 只保留指定区间内的元素,删除其他元素。 | + +> 更多命令请参考:[Redis List 类型官方命令文档](https://redis.io/commands#list) + +```shell +# 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面 +LPUSH key value [value ...] +# 将一个或多个值value插入到key列表的表尾(最右边) +RPUSH key value [value ...] +# 移除并返回key列表的头元素 +LPOP key +# 移除并返回key列表的尾元素 +RPOP key + +# 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始 +LRANGE key start stop + +# 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞 +BLPOP key [key ...] timeout +# 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞 +BRPOP key [key ...] timeout +``` + +### List 应用 + +#### 消息队列 + +消息队列在存取消息时,必须要满足三个需求,分别是**消息保序、处理重复的消息和保证消息可靠性**。 + +Redis 的 List 和 Stream 两种数据类型,就可以满足消息队列的这三个需求。我们先来了解下基于 List 的消息队列实现方法,后面在介绍 Stream 数据类型时候,在详细说说 Stream。 + +_1、如何满足消息保序需求?_ + +List 本身就是按先进先出的顺序对数据进行存取的,所以,如果使用 List 作为消息队列保存消息的话,就已经能满足消息保序的需求了。 + +List 可以使用 LPUSH + RPOP(或者反过来,RPUSH+LPOP)命令实现消息队列。 + +- 生产者使用 `LPUSH key value[value...]` 将消息插入到队列的头部,如果 key 不存在则会创建一个空的队列再插入消息。 + +- 消费者使用 `RPOP key` 依次读取队列的消息,先进先出。 + +不过,在消费者读取数据时,有一个潜在的性能风险点。 + +在生产者往 List 中写入数据时,List 并不会主动地通知消费者有新消息写入,如果消费者想要及时处理消息,就需要在程序中不停地调用 `RPOP` 命令(比如使用一个 while(1) 循环)。如果有新消息写入,RPOP 命令就会返回结果,否则,RPOP 命令返回空值,再继续循环。 + +所以,即使没有新消息写入 List,消费者也要不停地调用 RPOP 命令,这就会导致消费者程序的 CPU 一直消耗在执行 RPOP 命令上,带来不必要的性能损失。 + +为了解决这个问题,Redis 提供了 BRPOP 命令。**BRPOP 命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据**。和消费者程序自己不停地调用 RPOP 命令相比,这种方式能节省 CPU 开销。 + +_2、如何处理重复的消息?_ + +消费者要实现重复消息的判断,需要 2 个方面的要求: + +- 每个消息都有一个全局的 ID。 +- 消费者要记录已经处理过的消息的 ID。当收到一条消息后,消费者程序就可以对比收到的消息 ID 和记录的已处理过的消息 ID,来判断当前收到的消息有没有经过处理。如果已经处理过,那么,消费者程序就不再进行处理了。 + +但是 **List 并不会为每个消息生成 ID 号,所以我们需要自行为每个消息生成一个全局唯一 ID**,生成之后,我们在用 LPUSH 命令把消息插入 List 时,需要在消息中包含这个全局唯一 ID。 + +例如,我们执行以下命令,就把一条全局 ID 为 111000102、库存量为 99 的消息插入了消息队列: + +```shell +> LPUSH mq "111000102:stock:99" +(integer) 1 +``` + +_3、如何保证消息可靠性?_ + +当消费者程序从 List 中读取一条消息后,List 就不会再留存这条消息了。所以,如果消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么,消费者程序再次启动后,就没法再次从 List 中读取消息了。 + +为了留存消息,List 类型提供了 `BRPOPLPUSH` 命令,这个命令的**作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存**。 + +这样一来,如果消费者程序读了消息但没能正常处理,等它重启后,就可以从备份 List 中重新读取消息并进行处理了。 + +好了,到这里可以知道基于 List 类型的消息队列,满足消息队列的三大需求(消息保序、处理重复的消息和保证消息可靠性)。 + +- 消息保序:使用 LPUSH + RPOP; +- 阻塞读取:使用 BRPOP; +- 重复消息处理:生产者自行实现全局唯一 ID; +- 消息的可靠性:使用 BRPOPLPUSH + +> List 作为消息队列有什么缺陷? + +**List 不支持多个消费者消费同一条消息**,因为一旦消费者拉取一条消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费。 + +要实现一条消息可以被多个消费者消费,那么就要将多个消费者组成一个消费组,使得多个消费者可以消费同一条消息,但是 **List 类型并不支持消费组的实现**。 + +这就要说起 Redis 从 5.0 版本开始提供的 Stream 数据类型了,Stream 同样能够满足消息队列的三大需求,而且它还支持“消费组”形式的消息读取。 + +#### 输入自动补全 + +【需求场景】 + +根据用户输入,自动补全信息,如:联系人、商品名等。 + +- 典型场景一 - 社交网站后台记录用户最近联系过的 100 个好友,当用户查找好友时,根据输入的关键字自动补全姓名。 +- 典型场景二 - 电商网站后台记录用户最近浏览过的 10 件商品,当用户查找商品是,根据输入的关键字自动补全商品名称。 + +【解决方案】 + +> 使用 Redis 的 List 类型存储一个最近信息列表,然后在需要自动补全信息时展示相应数量的数据。 + +维护最近信息列表的常见操作如下: + +- 如果指定信息经存在于最近信息列表里,那么从列表里移除。使用 `LREM` 命令。 +- 将指定信息添加到最近信息列表的头部。使用 `LPUSH` 命令。 +- 添加操作完成后,如果最近信息列表中的数量超过上限 N,进行裁剪操作。使用 `LTRIM` 命令。 + +## Set + +Redis 中的 Set 类型就是无序且去重的集合。 + +### Set 简介 + +
+ +
+ +Set 类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。 + +一个集合最多可以存储 `2^32-1` 个元素。概念和数学中个的集合基本类似,可以交集,并集,差集等等,所以 Set 类型除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。 + +Set 类型和 List 类型的区别如下: + +- List 可以存储重复元素,Set 只能存储非重复元素; +- List 是按照元素的先后顺序存储元素的,而 Set 则是无序方式存储元素的。 + +### Set 实现 + +集合对象的编码可以是 `intset` 或者 `hashtable` 。 + +`intset` 编码的集合对象使用整数集合作为底层实现, 集合对象包含的所有元素都被保存在整数集合里面。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309241059483.png) + +`hashtable` 编码的集合对象使用字典作为底层实现, 字典的每个键都是一个字符串对象, 每个字符串对象包含了一个集合元素, 而字典的值则全部被设置为 `NULL` 。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309241100174.png) + +当集合对象可以同时满足以下两个条件时,集合对象使用 `intset` 编码;否则,使用 `hashtable` 编码: + +1. 集合对象保存的所有元素都是整数值; +2. 集合对象保存的元素数量不超过 `512` 个; + +> 注意:第二个条件的上限值是可以修改的, 具体请看配置文件中关于 `set-max-intset-entries` 选项的说明。 + +### Set 命令 + +| 命令 | 行为 | +| ----------- | ---------------------------------------------- | +| `SADD` | 将给定元素添加到集合。 | +| `SMEMBERS` | 返回集合包含的所有元素。 | +| `SISMEMBER` | 检查给定元素是否存在于集合中。 | +| `SREM` | 如果给定的元素存在于集合中,那么移除这个元素。 | + +> 更多命令请参考:[Redis Set 类型官方命令文档](https://redis.io/commands#set) + +Set 常用操作: + +```shell +# 往集合key中存入元素,元素存在则忽略,若key不存在则新建 +SADD key member [member ...] +# 从集合key中删除元素 +SREM key member [member ...] +# 获取集合key中所有元素 +SMEMBERS key +# 获取集合key中的元素个数 +SCARD key + +# 判断member元素是否存在于集合key中 +SISMEMBER key member + +# 从集合key中随机选出count个元素,元素不从key中删除 +SRANDMEMBER key [count] +# 从集合key中随机选出count个元素,元素从key中删除 +SPOP key [count] +``` + +Set 运算操作: + +```shell +# 交集运算 +SINTER key [key ...] +# 将交集结果存入新集合destination中 +SINTERSTORE destination key [key ...] + +# 并集运算 +SUNION key [key ...] +# 将并集结果存入新集合destination中 +SUNIONSTORE destination key [key ...] + +# 差集运算 +SDIFF key [key ...] +# 将差集结果存入新集合destination中 +SDIFFSTORE destination key [key ...] +``` + +### Set 应用 + +集合的主要几个特性,无序、不可重复、支持并交差等操作。 + +因此 Set 类型比较适合用来数据去重和保障数据的唯一性,还可以用来统计多个集合的交集、错集和并集等,当我们存储的数据是无序并且需要去重的情况下,比较适合使用集合类型进行存储。 + +但是要提醒你一下,这里有一个潜在的风险。**Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞**。 + +在主从集群中,为了避免主库因为 Set 做聚合计算(交集、差集、并集)时导致主库被阻塞,我们可以选择一个从库完成聚合统计,或者把数据返回给客户端,由客户端来完成聚合统计。 + +#### 点赞 + +Set 类型可以保证一个用户只能点一个赞,这里举例子一个场景,key 是文章 id,value 是用户 id。 + +`uid:1` 、`uid:2`、`uid:3` 三个用户分别对 article:1 文章点赞了。 + +```shell +# uid:1 用户对文章 article:1 点赞 +> SADD article:1 uid:1 +(integer) 1 +# uid:2 用户对文章 article:1 点赞 +> SADD article:1 uid:2 +(integer) 1 +# uid:3 用户对文章 article:1 点赞 +> SADD article:1 uid:3 +(integer) 1 +``` + +`uid:1` 取消了对 article:1 文章点赞。 + +```plain +> SREM article:1 uid:1 +(integer) 1 +``` + +获取 article:1 文章所有点赞用户 : + +```shell +> SMEMBERS article:1 +1) "uid:3" +2) "uid:2" +``` + +获取 article:1 文章的点赞用户数量: + +```shell +> SCARD article:1 +(integer) 2 +``` + +判断用户 `uid:1` 是否对文章 article:1 点赞了: + +```shell +> SISMEMBER article:1 uid:1 +(integer) 0 # 返回0说明没点赞,返回1则说明点赞了 +``` + +#### 共同关注 + +Set 类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。 + +key 可以是用户 id,value 则是已关注的公众号的 id。 + +`uid:1` 用户关注公众号 id 为 5、6、7、8、9,`uid:2` 用户关注公众号 id 为 7、8、9、10、11。 + +```shell +# uid:1 用户关注公众号 id 为 5、6、7、8、9 +> SADD uid:1 5 6 7 8 9 +(integer) 5 +# uid:2 用户关注公众号 id 为 7、8、9、10、11 +> SADD uid:2 7 8 9 10 11 +(integer) 5 +``` + +`uid:1` 和 `uid:2` 共同关注的公众号: + +```shell +# 获取共同关注 +> SINTER uid:1 uid:2 +1) "7" +2) "8" +3) "9" +``` + +给 `uid:2` 推荐 `uid:1` 关注的公众号: + +```shell +> SDIFF uid:1 uid:2 +1) "5" +2) "6" +``` + +验证某个公众号是否同时被 `uid:1` 或 `uid:2` 关注: + +```shell +> SISMEMBER uid:1 5 +(integer) 1 # 返回1,说明关注了 +> SISMEMBER uid:2 5 +(integer) 0 # 返回0,说明没关注 +``` + +#### 抽奖活动 + +存储某活动中中奖的用户名,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。 + +key 为抽奖活动名,value 为员工名称,把所有员工名称放入抽奖箱: + +```shell +>SADD lucky Tom Jerry John Sean Marry Lindy Sary Mark +(integer) 5 +``` + +如果允许重复中奖,可以使用 SRANDMEMBER 命令。 + +```shell +# 抽取 1 个一等奖: +> SRANDMEMBER lucky 1 +1) "Tom" +# 抽取 2 个二等奖: +> SRANDMEMBER lucky 2 +1) "Mark" +2) "Jerry" +# 抽取 3 个三等奖: +> SRANDMEMBER lucky 3 +1) "Sary" +2) "Tom" +3) "Jerry" +``` + +如果不允许重复中奖,可以使用 SPOP 命令。 + +```shell +# 抽取一等奖1个 +> SPOP lucky 1 +1) "Sary" +# 抽取二等奖2个 +> SPOP lucky 2 +1) "Jerry" +2) "Mark" +# 抽取三等奖3个 +> SPOP lucky 3 +1) "John" +2) "Sean" +3) "Lindy" +``` + +## Zset + +Redis 中的 Zset 类型就是有序且去重的集合。 + +### Zset 简介 + +Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。 + +有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。 + +
+ +
+ +### Zset 实现 + +有序集合的编码可以是 `ziplist` 或者 `skiplist` 。 + +`ziplist` 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。压缩列表内的集合元素按分值从小到大进行排序, 分值较小的元素被放置在靠近表头的方向, 而分值较大的元素则被放置在靠近表尾的方向。 + +`skiplist` 编码的有序集合对象使用 `zset` 结构作为底层实现, 一个 `zset` 结构同时包含一个字典和一个跳跃表 + +```c +typedef struct zset { + + zskiplist *zsl; + + dict *dict; + +} zset; +``` + +`zset` 结构中的 `zsl` 跳跃表按分值从小到大保存了所有集合元素, 每个跳跃表节点都保存了一个集合元素: 跳跃表节点的 `object` 属性保存了元素的成员, 而跳跃表节点的 `score` 属性则保存了元素的分值。 通过这个跳跃表, 程序可以对有序集合进行范围型操作, 比如 ZRANK 、 ZRANGE 等命令就是基于跳跃表 API 来实现的。 + +除此之外, `zset` 结构中的 `dict` 字典为有序集合创建了一个从成员到分值的映射, 字典中的每个键值对都保存了一个集合元素: 字典的键保存了元素的成员, 而字典的值则保存了元素的分值。 通过这个字典, 程序可以用 O(1) 复杂度查找给定成员的分值, ZSCORE 命令就是根据这一特性实现的, 而很多其他有序集合命令都在实现的内部用到了这一特性。 + +有序集合每个元素的成员都是一个字符串对象, 而每个元素的分值都是一个 `double` 类型的浮点数。 值得一提的是, 虽然 `zset` 结构同时使用跳跃表和字典来保存有序集合元素, 但这两种数据结构都会通过指针来共享相同元素的成员和分值, 所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值, 也不会因此而浪费额外的内存。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309241108347.png) + +当有序集合对象可以同时满足以下两个条件时,有序集合对象使用 `ziplist` 编码;否则,使用 `skiplist` 编码。 + +- 有序集合保存的元素数量小于 `128` 个; +- 有序集合保存的所有元素成员的长度都小于 `64` 字节; + +> 注意:以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 `zset-max-ziplist-entries` 选项和 `zset-max-ziplist-value` 选项的说明。 + +**在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。** + +### Zset 命令 + +| 命令 | 行为 | +| ---------------- | ------------------------------------------ | +| `ZADD` | 将一个带有给定分值的成员添加到有序集合里面 | +| `ZRANGE` | 顺序排序,并返回指定排名区间的成员 | +| ZREVRANGE | 反序排序,并返回指定排名区间的成员 | +| `ZRANGEBYSCORE` | 顺序排序,并返回指定排名区间的成员及其分值 | +| ZREVRANGEBYSCORE | 反序排序,并返回指定排名区间的成员及其分值 | +| `ZREM` | 移除指定的成员 | +| `ZSCORE` | 返回指定成员的分值 | +| `ZCARD` | 返回所有成员数 | + +> 更多命令请参考:[Redis ZSet 类型官方命令文档](https://redis.io/commands#sorted_set) + +Zset 常用操作: + +```shell +# 往有序集合key中加入带分值元素 +ZADD key score member [[score member]...] +# 往有序集合key中删除元素 +ZREM key member [member...] +# 返回有序集合key中元素member的分值 +ZSCORE key member +# 返回有序集合key中元素个数 +ZCARD key + +# 为有序集合key中元素member的分值加上increment +ZINCRBY key increment member + +# 正序获取有序集合key从start下标到stop下标的元素 +ZRANGE key start stop [WITHSCORES] +# 倒序获取有序集合key从start下标到stop下标的元素 +ZREVRANGE key start stop [WITHSCORES] + +# 返回有序集合中指定分数区间内的成员,分数由低到高排序。 +ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] + +# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。 +ZRANGEBYLEX key min max [LIMIT offset count] +# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同 +ZREVRANGEBYLEX key max min [LIMIT offset count] +``` + +Zset 运算操作(相比于 Set 类型,ZSet 类型没有支持差集运算): + +```shell +# 并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积 +ZUNIONSTORE destkey numberkeys key [key...] +# 交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积 +ZINTERSTORE destkey numberkeys key [key...] +``` + +### Zset 应用 + +Zset 类型(Sorted Set,有序集合)可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。比如说,我们可以根据元素插入 Sorted Set 的时间确定权重值,先插入的元素权重小,后插入的元素权重大。 + +在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,可以优先考虑使用 Sorted Set。 + +#### 排行榜 + +【需求场景】 + +各种排行榜,如:内容平台(视频、歌曲、文章)的播放量/收藏量/评分排行榜;电商网站的销售排行榜; + +【解决方案】 + +有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。 + +我们以博文点赞排名为例,小林发表了五篇博文,分别获得赞为 200、40、100、50、150。 + +```shell +# arcticle:1 文章获得了200个赞 +> ZADD user:xiaolin:ranking 200 arcticle:1 +(integer) 1 +# arcticle:2 文章获得了40个赞 +> ZADD user:xiaolin:ranking 40 arcticle:2 +(integer) 1 +# arcticle:3 文章获得了100个赞 +> ZADD user:xiaolin:ranking 100 arcticle:3 +(integer) 1 +# arcticle:4 文章获得了50个赞 +> ZADD user:xiaolin:ranking 50 arcticle:4 +(integer) 1 +# arcticle:5 文章获得了150个赞 +> ZADD user:xiaolin:ranking 150 arcticle:5 +(integer) 1 +``` + +文章 arcticle:4 新增一个赞,可以使用 ZINCRBY 命令(为有序集合 key 中元素 member 的分值加上 increment): + +```shell +> ZINCRBY user:xiaolin:ranking 1 arcticle:4 +"51" +``` + +查看某篇文章的赞数,可以使用 ZSCORE 命令(返回有序集合 key 中元素个数): + +```shell +> ZSCORE user:xiaolin:ranking arcticle:4 +"50" +``` + +获取小林文章赞数最多的 3 篇文章,可以使用 ZREVRANGE 命令(倒序获取有序集合 key 从 start 下标到 stop 下标的元素): + +```shell +# WITHSCORES 表示把 score 也显示出来 +> ZREVRANGE user:xiaolin:ranking 0 2 WITHSCORES +1) "arcticle:1" +2) "200" +3) "arcticle:5" +4) "150" +5) "arcticle:3" +6) "100" +``` + +获取小林 100 赞到 200 赞的文章,可以使用 ZRANGEBYSCORE 命令(返回有序集合中指定分数区间内的成员,分数由低到高排序): + +```shell +> ZRANGEBYSCORE user:xiaolin:ranking 100 200 WITHSCORES +1) "arcticle:3" +2) "100" +3) "arcticle:5" +4) "150" +5) "arcticle:1" +6) "200" +``` + +#### 前缀排序 + +使用有序集合的 `ZRANGEBYLEX` 或 `ZREVRANGEBYLEX` 可以帮助我们实现电话号码或姓名的排序,我们以 `ZRANGEBYLEX` (返回指定成员区间内的成员,按 key 正序排列,分数必须相同)为例。 + +**注意:不要在分数不一致的 SortSet 集合中去使用 ZRANGEBYLEX 和 ZREVRANGEBYLEX 指令,因为获取的结果会不准确。** + +_1、电话排序_ + +我们可以将电话号码存储到 SortSet 中,然后根据需要来获取号段: + +```shell +> ZADD phone 0 13100111100 0 13110114300 0 13132110901 +(integer) 3 +> ZADD phone 0 13200111100 0 13210414300 0 13252110901 +(integer) 3 +> ZADD phone 0 13300111100 0 13310414300 0 13352110901 +(integer) 3 +``` + +获取所有号码: + +```shell +> ZRANGEBYLEX phone - + +1) "13100111100" +2) "13110114300" +3) "13132110901" +4) "13200111100" +5) "13210414300" +6) "13252110901" +7) "13300111100" +8) "13310414300" +9) "13352110901" +``` + +获取 132 号段的号码: + +```shell +> ZRANGEBYLEX phone [132 (133 +1) "13200111100" +2) "13210414300" +3) "13252110901" +``` + +获取 132、133 号段的号码: + +```shell +> ZRANGEBYLEX phone [132 (134 +1) "13200111100" +2) "13210414300" +3) "13252110901" +4) "13300111100" +5) "13310414300" +6) "13352110901" +``` + +_2、姓名排序_ + +```shell +> zadd names 0 Toumas 0 Jake 0 Bluetuo 0 Gaodeng 0 Aimini 0 Aidehua +(integer) 6 +``` + +获取所有人的名字: + +```shell +> ZRANGEBYLEX names - + +1) "Aidehua" +2) "Aimini" +3) "Bluetuo" +4) "Gaodeng" +5) "Jake" +6) "Toumas" +``` + +获取名字中大写字母 A 开头的所有人: + +```shell +> ZRANGEBYLEX names [A (B +1) "Aidehua" +2) "Aimini" +``` + +获取名字中大写字母 C 到 Z 的所有人: + +```shell +> ZRANGEBYLEX names [C [Z +1) "Gaodeng" +2) "Jake" +3) "Toumas" +``` + +## 总结 + +Redis 常见的五种数据类型:**String(字符串),Hash(哈希),List(列表),Set(集合)及 Zset(sorted set:有序集合)**。 + +这五种数据类型都由多种数据结构实现的,主要是出于时间和空间的考虑,当数据量小的时候使用更简单的数据结构,有利于节省内存,提高性能。 + +可以看到,Redis 数据类型的底层数据结构随着版本的更新也有所不同,比如: + +- 在 Redis 3.0 版本中 List 对象的底层数据结构由“双向链表”或“压缩表列表”实现,但是在 3.2 版本之后,List 数据类型底层数据结构是由 quicklist 实现的; +- 在最新的 Redis 代码中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。 + +Redis 五种数据类型的应用场景: + +- String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。 +- List 类型的应用场景:消息队列(有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。 +- Hash 类型:缓存对象、购物车等。 +- Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。 +- Zset 类型:排序场景,比如排行榜、电话和姓名排序等。 + +Redis 后续版本又支持四种数据类型,它们的应用场景如下: + +- BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等; +- HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等; +- GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车; +- Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息 ID,支持以消费组形式消费数据。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309232144470.jpg) + +针对 Redis 是否适合做消息队列,关键看你的业务场景: + +- 如果你的业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把 Redis 当作队列是完全可以的。 +- 如果你的业务有海量消息,消息积压的概率比较大,并且不能接受数据丢失,那么还是用专业的消息队列中间件吧。 + +## 参考资料 + +- [《Redis 实战》](https://item.jd.com/11791607.html) +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) +- [Redis 常见数据类型和应用场景](https://xiaolincoding.com/redis/data_struct/command.html#string) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" deleted file mode 100644 index 0fa0bdf265..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/01.Redis\351\235\242\350\257\225\346\200\273\347\273\223.md" +++ /dev/null @@ -1,291 +0,0 @@ ---- -title: Redis 面试总结 -date: 2020-07-13 17:03:42 -categories: - - 数据库 - - KV数据库 - - Redis -tags: - - 数据库 - - KV数据库 - - Redis - - 面试 -permalink: /pages/451b73/ ---- - -# Redis 面试总结 - -## Redis 数据类型 - -【问题】 - -- Redis 有哪些数据类型? -- Redis 的数据类型分别适用于什么样的场景? - ---- - -【解答】 - -> **_Redis 数据类型和应用_** -> -> 数据类型的特性和应用细节点较多,详情可以参考:[Redis 数据类型](https://github.com/dunwu/db-tutorial/blob/master/docs/nosql/redis/redis-datatype.md) - -(1)Redis 支持五种基本数据类型: - -- String:常用于 KV 缓存 -- Hash:存储结构化数据,如:产品信息、用户信息等。 -- List:存储列表,如:粉丝列表、文章评论列表等。可以通过 lrange 命令进行分页查询。 -- Set:存储去重列表,如:粉丝列表等。可以基于 set 玩儿交集、并集、差集的操作。例如:求两个人的共同好友列表。 -- Sorted Set:存储含评分的去重列表,如:各种排行榜。 - -(2)除此以外,还有 Bitmaps、HyperLogLogs、GEO、Streams 等高级数据类型。 - -## Redis 内存淘汰 - -【问题】 - -- Redis 有哪些内存淘汰策略? -- 这些淘汰策略分别适用于什么场景? -- Redis 有哪些删除失效 key 的方法? -- 如何设置 Redis 中键的过期时间? -- 如果让你实现一个 LRU 算法,怎么做? - ---- - -【解答】 - -(1)Redis 过期策略是:**定期删除+惰性删除**。 - -- 消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它。 -- 主动方法(active way),定期从设置了失效时间的主键中选择一部分失效的主键删除。 - -(2)Redis 内存淘汰策略: - -- **`noeviction`** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。这是 Redis 默认的策略。 -- **`allkeys-lru`** - 在主键空间中,优先移除最近未使用的 key。 -- **`allkeys-random`** - 在主键空间中,随机移除某个 key。 -- **`volatile-lru`** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。 -- **`volatile-random`** - 在设置了过期时间的键空间中,随机移除某个 key。 -- **`volatile-ttl`** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。 - -(3)如何选择内存淘汰策略: - -- 如果数据呈现幂等分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 `allkeys-lru`。 -- 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 `allkeys-random`。 -- `volatile-lru` 策略和 `volatile-random` 策略适合我们将一个 Redis 实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个 Redis 实例来达到相同的效果。 -- 将 key 设置过期时间实际上会消耗更多的内存,因此我们建议使用 `allkeys-lru` 策略从而更有效率的使用内存。 - -(4)LRU 算法实现思路:可以继承 LinkedHashMap,并覆写 removeEldestEntry 方法来实现一个最简单的 LRUCache - -## Redis 持久化 - -【问题】 - -- Redis 有几种持久化方式? -- Redis 的不同持久化方式的特性和原理是什么? -- RDB 和 AOF 各有什么优缺点?分别适用于什么样的场景? -- Redis 执行持久化时,可以处理请求吗? -- AOF 有几种同步频率? - ---- - -【解答】 - -> **_Redis 持久化_** -> -> 详情可以参考:[Redis 持久化](04.Redis持久化.md) - -(1)Redis 支持两种持久化方式:RDB 和 AOF。 - -(2)RDB 即某一时刻的二进制数据快照。 - -Redis 会周期性生成 RDB 文件。 - -生成 RDB 流程:Redis fork 一个子进程,负责生成 RDB;生成 RDB 采用 Copy On Write 模式,此时,如果收到写请求,会在原副本上操作,不影响工作。 - -RDB 只能恢复生成快照时刻的数据,之后的数据无法恢复。生成 RDB 的资源开销高昂。RDB 适合做冷备。 - -(3)AOF 会将写命令不断追加到 AOF 文本日志末尾。 - -AOF 丢数据比 RDB 少,但文件会比 RDB 文件大很多。 - -一般,AOF 设置 `appendfsync` 同步频率为 **`everysec`** 即可。 - -(4)RDB or AOF - -建议同时使用 RDB 和 AOF。用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。 - -## Redis 事务 - -【问题】 - -- Redis 的并发竞争问题是什么?如何解决这个问题? -- Redis 支持事务吗? -- Redis 事务是严格意义的事务吗?Redis 为什么不支持回滚。 -- Redis 事务如何工作? -- 了解 Redis 事务中的 CAS 行为吗? - -【解答】 - -> **_Redis 的事务特性、原理_** -> -> 详情参考:[Redis 应用指南之 事务](02.Redis应用指南.md#六redis-事务) - -**Redis 提供的不是严格的事务,Redis 只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去**。 - -Redis 不支持回滚的理由: - -- Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面。 -- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 - -`MULTI` 、 `EXEC` 、 `DISCARD` 和 `WATCH` 是 Redis 事务相关的命令。 - -Redis 有天然解决这个并发竞争问题的类 CAS 乐观锁方案:每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。 - -## Redis 管道 - -【问题】 - -- 除了事务,还有其他批量执行 Redis 命令的方式吗? - -【解答】 - -Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。Redis 支持管道技术。管道技术允许请求以异步方式发送,即旧请求的应答还未返回的情况下,允许发送新请求。这种方式可以大大提高传输效率。使用管道发送命令时,Redis Server 会将部分请求放到缓存队列中(占用内存),执行完毕后一次性发送结果。如果需要发送大量的命令,会占用大量的内存,因此应该按照合理数量分批次的处理。 - -## Redis 高并发 - -【问题】 - -- Redis 是单线程模型,为何吞吐量还很高? -- Redis 的 IO 多路复用原理是什么? -- Redis 集群如何分片和寻址? -- Redis 集群如何扩展? -- Redis 集群如何保证数据一致? -- Redis 集群如何规划?你们公司的生产环境上如何部署 Redis 集群? - ---- - -【解答】 - -> **_Redis 集群_** -> -> 详情可以参考:[Redis 集群](07.Redis集群.md) - -(1)单线程 - -Redis 为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis 单机吞吐量也很高,能达到几万 QPS。 - -Redis 单线程模型,依然有很高的并发吞吐,原因在于: - -- Redis 读写都是内存操作。 -- Redis 基于**非阻塞的 IO 多路复用机制**,同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。 -- 单线程,避免了线程创建、销毁、上下文切换的开销,并且避免了资源竞争。 - -(2)扩展并发吞吐量、存储容量 - -Redis 的高性能(扩展并发吞吐量、存储容量)通过主从架构来实现。 - -Redis 集群采用主从模型,提供复制和故障转移功能,来保证 Redis 集群的高可用。通常情况,一主多从模式已经可以满足大部分项目的需要。根据实际的并发量,可以通过增加节点来扩展并发吞吐。 - -一主多从模式下,主节点负责写操作(单机几万 QPS),从节点负责查询操作(单机十万 QPS)。 - -进一步,如果需要缓存大量数据,就需要分区(sharding)。Redis 集群通过划分虚拟 hash 槽来分片,每个主节点负责一定范围的 hash 槽。当需要扩展集群节点时,重新分配 hash 槽即可,redis-trib 会自动迁移变更 hash 槽中所属的 key。 - -(3)Redis 集群数据一致性 - -Redis 集群基于复制特性实现节点间的数据一致性。 - -## Redis 复制 - -【问题】 - -- Redis 复制的工作原理?Redis 旧版复制和新版复制有何不同? -- Redis 主从节点间如何复制数据? -- Redis 的数据一致性是强一致性吗? - ---- - -【解答】 - -> **_Redis 复制_** -> -> 详情可以参考:[Redis 复制](05.Redis复制.md) - -(1)旧版复制基于 `SYNC` 命令实现。分为同步(sync)和命令传播(command propagate)两个操作。这种方式存在缺陷:不能高效处理断线重连后的复制情况。 - -(2)新版复制基于 `PSYNC` 命令实现。同步操作分为了两块: - -- **`完整重同步(full resychronization)`** 用于初次复制; -- **`部分重同步(partial resychronization)`** 用于断线后重复制。 - - 主从服务器的**复制偏移量(replication offset)** - - 主服务器的**复制积压缓冲区(replication backlog)** - - **服务器的运行 ID** - -(3)Redis 集群主从节点复制的工作流程: - -- 步骤 1. 设置主从服务器 -- 步骤 2. 主从服务器建立 TCP 连接。 -- 步骤 3. 发送 PING 检查通信状态。 -- 步骤 4. 身份验证。 -- 步骤 5. 发送端口信息。 -- 步骤 6. 同步。 -- 步骤 7. 命令传播。 - -## Redis 哨兵 - -【问题】 - -- Redis 如何实现高可用? -- Redis 哨兵的功能? -- Redis 哨兵的原理? -- Redis 哨兵如何选举 Leader? -- Redis 如何实现故障转移? - ---- - -【解答】 - -> **_Redis 哨兵_** -> -> 详情可以参考:[Redis 哨兵](06.Redis哨兵.md) - -(1)Redis 的高可用是通过哨兵来实现(Raft 协议的 Redis 实现)。Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 - -由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131135847.png) - -## Redis vs. Memcached - -【问题】 - -Redis 和 Memcached 有什么区别? - -分布式缓存技术选型,选 Redis 还是 Memcached,为什么? - -Redis 和 Memcached 各自的线程模型是怎样的? - -为什么单线程的 Redis 性能却不输于多线程的 Memcached? - -【解答】 - -Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。memcache 支持简单的数据类型,String。 - -Redis 支持数据的备份,即 master-slave 模式的数据备份。 - -Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中 - -redis 的速度比 memcached 快很多 - -Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的 IO 复用模型。 - -![Redis与Memcached的区别与比较](https://user-gold-cdn.xitu.io/2018/4/18/162d7773080d4570?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -如果想要更详细了解的话,可以查看慕课网上的这篇手记(非常推荐) **:《脚踏两只船的困惑 - Memcached 与 Redis》**:[www.imooc.com/article/23549](https://www.imooc.com/article/23549) - -**终极策略:** 使用 Redis 的 String 类型做的事,都可以用 Memcached 替换,以此换取更好的性能提升; 除此以外,优先考虑 Redis; - -## 参考资料 - -- [面试中关于 Redis 的问题看这篇就够了](https://juejin.im/post/5ad6e4066fb9a028d82c4b66) -- [advanced-java](https://github.com/doocs/advanced-java#缓存) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" deleted file mode 100644 index c449419a98..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\345\272\224\347\224\250\346\214\207\345\215\227.md" +++ /dev/null @@ -1,511 +0,0 @@ ---- -title: Redis 应用指南 -date: 2020-01-30 21:48:57 -categories: - - 数据库 - - KV数据库 - - Redis -tags: - - 数据库 - - KV数据库 - - Redis -permalink: /pages/94e9d6/ ---- - -# Redis 应用指南 - -## 一、Redis 简介 - -> Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。 -> -> 键的类型只能为字符串,值支持的五种类型数据类型为:字符串、列表、集合、有序集合、散列表。 - -### Redis 使用场景 - -- **缓存** - 将热点数据放到内存中,设置内存的最大使用量以及过期淘汰策略来保证缓存的命中率。 -- **计数器** - Redis 这种内存数据库能支持计数器频繁的读写操作。 -- **应用限流** - 限制一个网站访问流量。 -- **消息队列** - 使用 List 数据类型,它是双向链表。 -- **查找表** - 使用 HASH 数据类型。 -- **交集运算** - 使用 SET 类型,例如求两个用户的共同好友。 -- **排行榜** - 使用 ZSET 数据类型。 -- **分布式 Session** - 多个应用服务器的 Session 都存储到 Redis 中来保证 Session 的一致性。 -- **分布式锁** - 除了可以使用 SETNX 实现分布式锁之外,还可以使用官方提供的 RedLock 分布式锁实现。 - -### Redis 的优势 - -- 性能极高 – Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s。 -- 丰富的数据类型 - 支持字符串、列表、集合、有序集合、散列表。 -- 原子 - Redis 的所有操作都是原子性的。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC 指令包起来。 -- 持久化 - Redis 支持数据的持久化。可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 -- 备份 - Redis 支持数据的备份,即 master-slave 模式的数据备份。 -- 丰富的特性 - Redis 还支持发布订阅, 通知, key 过期等等特性。 - -### Redis 与 Memcached - -Redis 与 Memcached 因为都可以用于缓存,所以常常被拿来做比较,二者主要有以下区别: - -**数据类型** - -- Memcached 仅支持字符串类型; -- 而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。 - -**数据持久化** - -- Memcached 不支持持久化; -- Redis 支持两种持久化策略:RDB 快照和 AOF 日志。 - -**分布式** - -- Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。 -- Redis Cluster 实现了分布式的支持。 - -**内存管理机制** - -- Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 -- 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。 - -### Redis 为什么快 - -Redis 单机 QPS 能达到 100000。 - -Redis 是单线程模型(Redis 6.0 已经支持多线程模型),为什么还能有这么高的并发? - -- Redis 完全基于内存操作。 -- Redis 数据结构简单。 -- 采用单线程,避免线程上下文切换和竞争。 -- 使用 I/O 多路复用模型(非阻塞 I/O)。 - -> I/O 多路复用 -> -> I/O 多路复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。 - -## 二、Redis 数据类型 - -Redis 基本数据类型:STRING、HASH、LIST、SET、ZSET - -Redis 高级数据类型:BitMap、HyperLogLog、GEO - -> :bulb: 更详细的特性及原理说明请参考:[Redis 数据类型和应用](03.Redis数据类型和应用.md) - -## 三、Redis 内存淘汰 - -### 内存淘汰要点 - -- **最大缓存** - Redis 允许通过 `maxmemory` 参数来设置内存最大值。 - -- **失效时间** - 作为一种定期清理无效数据的重要机制,在 Redis 提供的诸多命令中,`EXPIRE`、`EXPIREAT`、`PEXPIRE`、`PEXPIREAT` 以及 `SETEX` 和 `PSETEX` 均可以用来设置一条键值对的失效时间。而一条键值对一旦被关联了失效时间就会在到期后自动删除(或者说变得无法访问更为准确)。 - -- **淘汰策略** - 随着不断的向 Redis 中保存数据,当内存剩余空间无法满足添加的数据时,Redis 内就会施行数据淘汰策略,清除一部分内容然后保证新的数据可以保存到内存中。内存淘汰机制是为了更好的使用内存,用一定得 miss 来换取内存的利用率,保证 Redis 缓存中保存的都是热点数据。 - -- **非精准的 LRU** - 实际上 Redis 实现的 LRU 并不是可靠的 LRU,也就是名义上我们使用 LRU 算法淘汰键,但是实际上被淘汰的键并不一定是真正的最久没用的。 - -### 主键过期时间 - -Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。 - -对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。 - -可以使用 `EXPIRE` 或 `EXPIREAT` 来为 key 设置过期时间。 - -> 🔔 注意:当 `EXPIRE` 的时间如果设置的是负数,`EXPIREAT` 设置的时间戳是过期时间,将直接删除 key。 - -示例: - -```shell -redis> SET mykey "Hello" -"OK" -redis> EXPIRE mykey 10 -(integer) 1 -redis> TTL mykey -(integer) 10 -redis> SET mykey "Hello World" -"OK" -redis> TTL mykey -(integer) -1 -redis> -``` - -### 淘汰策略 - -内存淘汰只是 Redis 提供的一个功能,为了更好地实现这个功能,必须为不同的应用场景提供不同的策略,内存淘汰策略讲的是为实现内存淘汰我们具体怎么做,要解决的问题包括淘汰键空间如何选择?在键空间中淘汰键如何选择? - -Redis 提供了下面几种内存淘汰策略供用户选: - -- **`noeviction`** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。这是 Redis 默认的策略。 -- **`allkeys-lru`** - 在主键空间中,优先移除最近未使用的 key。 -- **`allkeys-random`** - 在主键空间中,随机移除某个 key。 -- **`volatile-lru`** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。 -- **`volatile-random`** - 在设置了过期时间的键空间中,随机移除某个 key。 -- **`volatile-ttl`** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。 - -### 如何选择淘汰策略 - -- 如果**数据呈现幂等分布(存在热点数据,部分数据访问频率高,部分数据访问频率低),则使用 `allkeys-lru`**。 -- 如果**数据呈现平等分布(数据访问频率大致相同),则使用 `allkeys-random`**。 -- 如果希望**使用不同的 TTL 值向 Redis 提示哪些 key 更适合被淘汰,请使用 `volatile-ttl`**。 -- **`volatile-lru` 和 `volatile-random` 适合既应用于缓存和又应用于持久化存储的场景**,然而我们也可以通过使用两个 Redis 实例来达到相同的效果。 -- **将 key 设置过期时间实际上会消耗更多的内存,因此建议使用 `allkeys-lru` 策略从而更有效率的使用内存**。 - -### 内部实现 - -Redis 删除失效主键的方法主要有两种: - -- 消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它。 -- 主动方法(active way),周期性地从设置了失效时间的主键中选择一部分失效的主键删除。 -- 主动删除:当前已用内存超过 `maxmemory` 限定时,触发主动清理策略,该策略由启动参数的配置决定主键具体的失效时间全部都维护在 `expires` 这个字典表中。 - -## 四、Redis 持久化 - -Redis 是内存型数据库,为了保证数据在宕机后不会丢失,需要将内存中的数据持久化到硬盘上。 - -Redis 支持两种持久化方式:RDB 和 AOF。 - -- RDB - **RDB 即快照方式,它将某个时间点的所有 Redis 数据保存到一个经过压缩的二进制文件(RDB 文件)中**。 -- AOF - `AOF(Append Only File)` 是以文本日志形式将所有写命令追加到 AOF 文件的末尾,以此来记录数据的变化。当服务器重启的时候会重新载入和执行这些命令来恢复原始的数据。AOF 适合作为 **热备**。 - -> :bulb: 更详细的特性及原理说明请参考:[Redis 持久化](04.Redis持久化.md) - -## 五、Redis 事件 - -Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: - -- **`文件事件(file event)`** - Redis 服务器通过套接字(Socket)与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。服务器与客户端(或其他的服务器)的通信会产生文件事件,而服务器通过监听并处理这些事件来完成一系列网络通信操作。 -- **`时间事件(time event)`** - Redis 服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。 - -### 文件事件 - -Redis 基于 Reactor 模式开发了自己的网络时间处理器。 - -- Redis 文件事件处理器使用 I/O 多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。 -- 当被监听的套接字准备好执行连接应答、读取、写入、关闭操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。 - -虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器实现了高性能的网络通信模型。 - -文件事件处理器有四个组成部分:套接字、I/O 多路复用程序、文件事件分派器、事件处理器。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200130172525.png) - -### 时间事件 - -时间事件又分为: - -- **定时事件**:是让一段程序在指定的时间之内执行一次; -- **周期性事件**:是让一段程序每隔指定时间就执行一次。 - -Redis 将所有时间事件都放在一个无序链表中,每当时间事件执行器运行时,通过遍历整个链表查找出已到达的时间事件,并调用响应的事件处理器。 - -### 事件的调度与执行 - -服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能一直监听,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。 - -事件调度与执行由 aeProcessEvents 函数负责,伪代码如下: - -```python -def aeProcessEvents(): - - ## 获取到达时间离当前时间最接近的时间事件 - time_event = aeSearchNearestTimer() - - ## 计算最接近的时间事件距离到达还有多少毫秒 - remaind_ms = time_event.when - unix_ts_now() - - ## 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0 - if remaind_ms < 0: - remaind_ms = 0 - - ## 根据 remaind_ms 的值,创建 timeval - timeval = create_timeval_with_ms(remaind_ms) - - ## 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定 - aeApiPoll(timeval) - - ## 处理所有已产生的文件事件 - procesFileEvents() - - ## 处理所有已到达的时间事件 - processTimeEvents() -``` - -将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下: - -```python -def main(): - - ## 初始化服务器 - init_server() - - ## 一直处理事件,直到服务器关闭为止 - while server_is_not_shutdown(): - aeProcessEvents() - - ## 服务器关闭,执行清理操作 - clean_server() -``` - -从事件处理的角度来看,服务器运行流程如下: - -
- -
- -## 六、Redis 事务 - -> **Redis 提供的不是严格的事务,Redis 只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去**。 - -`MULTI` 、 `EXEC` 、 `DISCARD` 和 `WATCH` 是 Redis 事务相关的命令。 - -事务可以一次执行多个命令, 并且有以下两个重要的保证: - -- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 -- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 - -### MULTI - -**[`MULTI`](https://redis.io/commands/multi) 命令用于开启一个事务,它总是返回 OK 。** - -`MULTI` 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC 命令被调用时, 所有队列中的命令才会被执行。 - -以下是一个事务例子, 它原子地增加了 foo 和 bar 两个键的值: - -```python -> MULTI -OK -> INCR foo -QUEUED -> INCR bar -QUEUED -> EXEC -1) (integer) 1 -2) (integer) 1 -``` - -### EXEC - -**[`EXEC`](https://redis.io/commands/exec) 命令负责触发并执行事务中的所有命令。** - -- 如果客户端在使用 `MULTI` 开启了一个事务之后,却因为断线而没有成功执行 `EXEC` ,那么事务中的所有命令都不会被执行。 -- 另一方面,如果客户端成功在开启事务之后执行 `EXEC` ,那么事务中的所有命令都会被执行。 - -### DISCARD - -**当执行 [`DISCARD`](https://redis.io/commands/discard) 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出。** - -示例: - -```python -> SET foo 1 -OK -> MULTI -OK -> INCR foo -QUEUED -> DISCARD -OK -> GET foo -"1" -``` - -### WATCH - -**[`WATCH`](https://redis.io/commands/watch) 命令可以为 Redis 事务提供 check-and-set (CAS)行为。** - -被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回 nil-reply 来表示事务已经失败。 - -```python -WATCH mykey -val = GET mykey -val = val + 1 -MULTI -SET mykey $val -EXEC -``` - -使用上面的代码, 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。 - -这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。 - -WATCH 使得 EXEC 命令需要有条件地执行:事务只能在所有被监视键都没有被修改的前提下执行,如果这个前提不能满足的话,事务就不会被执行。 - -WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。 - -用户还可以在单个 WATCH 命令中监视任意多个键,例如: - -```python -redis> WATCH key1 key2 key3 -OK -``` - -#### 取消 WATCH 的场景 - -当 EXEC 被调用时, 不管事务是否成功执行, 对所有键的监视都会被取消。 - -另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。 - -使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。 - -#### 使用 WATCH 创建原子操作 - -WATCH 可以用于创建 Redis 没有内置的原子操作。 - -举个例子,以下代码实现了原创的 ZPOP 命令,它可以原子地弹出有序集合中分值(score)最小的元素: - -``` -WATCH zset -element = ZRANGE zset 0 0 -MULTI -ZREM zset element -EXEC -``` - -### Rollback - -**Redis 不支持回滚**。Redis 不支持回滚的理由: - -- Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面。 -- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 - -## 七、Redis 管道 - -Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。Redis 支持管道技术。管道技术允许请求以异步方式发送,即旧请求的应答还未返回的情况下,允许发送新请求。这种方式可以大大提高传输效率。 - -在需要批量执行 Redis 命令时,如果一条一条执行,显然很低效。为了减少通信次数并降低延迟,可以使用 Redis 管道功能。Redis 的管道(pipeline)功能没有提供命令行支持,但是在各种语言版本的客户端中都有相应的实现。 - -以 Jedis 为例: - -```java -Pipeline pipe = conn.pipelined(); -pipe.multi(); -pipe.hset("login:", token, user); -pipe.zadd("recent:", timestamp, token); -if (item != null) { - pipe.zadd("viewed:" + token, timestamp, item); - pipe.zremrangeByRank("viewed:" + token, 0, -26); - pipe.zincrby("viewed:", -1, item); -} -pipe.exec(); -``` - -> :bell: 注意:使用管道发送命令时,Redis Server 会将部分请求放到缓存队列中(占用内存),执行完毕后一次性发送结果。如果需要发送大量的命令,会占用大量的内存,因此应该按照合理数量分批次的处理。 - -## 八、Redis 发布与订阅 - -Redis 提供了 5 个发布与订阅命令: - -| 命令 | 描述 | -| -------------- | ------------------------------------------------------------------- | -| `SUBSCRIBE` | `SUBSCRIBE channel [channel ...]`—订阅指定频道。 | -| `UNSUBSCRIBE` | `UNSUBSCRIBE [channel [channel ...]]`—取消订阅指定频道。 | -| `PUBLISH` | `PUBLISH channel message`—发送信息到指定的频道。 | -| `PSUBSCRIBE` | `PSUBSCRIBE pattern [pattern ...]`—订阅符合指定模式的频道。 | -| `PUNSUBSCRIBE` | `PUNSUBSCRIBE [pattern [pattern ...]]`—取消订阅符合指定模式的频道。 | - -订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。 - -某个客户端使用 SUBSCRIBE 订阅一个频道,其它客户端可以使用 PUBLISH 向这个频道发送消息。 - -发布与订阅模式和观察者模式有以下不同: - -- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。 -- 观察者模式是同步的,当事件触发时,主题会去调用观察者的方法;而发布与订阅模式是异步的; - ---- - -**_分割线以下为 Redis 集群功能特性_** - -## 九、Redis 复制 - -> 关系型数据库通常会使用一个主服务器向多个从服务器发送更新,并使用从服务器来处理所有读请求,Redis 也采用了同样的方式来实现复制特性。 - -### 旧版复制 - -Redis 2.8 版本以前的复制功能基于 `SYNC` 命令实现。 - -Redis 的复制功能分为同步(sync)和命令传播(command propagate)两个操作: - -- **`同步(sync)`** - 用于将从服务器的数据库状态更新至主服务器当前的数据库状态。 -- **`命令传播(command propagate)`** - 当主服务器的数据库状态被修改,导致主从数据库状态不一致时,让主从服务器的数据库重新回到一致状态。 - -这种方式存在缺陷:不能高效处理断线重连后的复制情况。 - -### 新版复制 - -Redis 2.8 版本以后的复制功能基于 `PSYNC` 命令实现。`PSYNC` 命令具有完整重同步和部分重同步两种模式。 - -- **`完整重同步(full resychronization)`** - 用于初次复制。执行步骤与 `SYNC` 命令基本一致。 -- **`部分重同步(partial resychronization)`** - 用于断线后重复制。**如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器**,从服务器只需接收并执行这些写命令,即可将主从服务器的数据库状态保持一致。 - -### 部分重同步 - -部分重同步有三个组成部分: - -- 主从服务器的**复制偏移量(replication offset)** -- 主服务器的**复制积压缓冲区(replication backlog)** -- **服务器的运行 ID** - -### PSYNC 命令 - -从服务器向要复制的主服务器发送 `PSYNC ` 命令 - -- 假如主从服务器的 **master run id 相同**,并且**指定的偏移量(offset)在内存缓冲区中还有效**,复制就会从上次中断的点开始继续。 -- 如果其中一个条件不满足,就会进行完全重新同步。 - -### 心跳检测 - -主服务器通过向从服务传播命令来更新从服务器状态,保持主从数据一致。 - -从服务器通过向主服务器发送命令 `REPLCONF ACK ` 来进行心跳检测,以及命令丢失检测。 - -> :bulb: 更详细的特性及原理说明请参考:[Redis 复制](05.Redis复制.md) - -## 十、Redis 哨兵 - -Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 - -> 💡 更详细的特性及原理说明请参考:[Redis 哨兵](06.Redis哨兵.md) - -## 十一、Redis 集群 - -分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,也可以从多台机器里面获取数据,这种方法在解决某些问题时可以获得线性级别的性能提升。 - -假设有 4 个 Reids 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。 - -主要有三种分片方式: - -- 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。 -- 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。 -- 服务器分片:Redis Cluster(官方的 Redis 集群解决方案)。 - -## 十二、Redis Client - -Redis 社区中有多种编程语言的客户端,可以在这里查找合适的客户端:[Redis 官方罗列的客户端清单](https://redis.io/clients) - -redis 官方推荐的 Java Redis Client: - -- [jedis](https://github.com/xetorthio/jedis) - 最流行的 Redis Java 客户端 -- [redisson](https://github.com/redisson/redisson) - 额外提供了很多的分布式服务特性,如:分布式锁、分布式 Java 常用对象(BitSet、BlockingQueue、CountDownLatch 等) -- [lettuce](https://github.com/lettuce-io/lettuce-core) - Spring Boot 2.0 默认 Redis 客户端 - -## 扩展阅读 - -> 💡 Redis 常用于分布式缓存,有关缓存的特性和原理请参考:[缓存基本原理](https://dunwu.github.io/design/distributed/分布式缓存.html) - -## 参考资料 - -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **教程** - - [Redis 命令参考](http://redisdoc.com/) -- **资源汇总** - - [awesome-redis](https://github.com/JamzyWang/awesome-redis) -- **Redis Client** - - [spring-data-redis 官方文档](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/) - - [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) - - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) - - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) - - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\351\253\230\347\272\247\346\225\260\346\215\256\347\261\273\345\236\213.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\351\253\230\347\272\247\346\225\260\346\215\256\347\261\273\345\236\213.md" new file mode 100644 index 0000000000..56734e213e --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/02.Redis\351\253\230\347\272\247\346\225\260\346\215\256\347\261\273\345\236\213.md" @@ -0,0 +1,566 @@ +--- +icon: logos:redis +title: Redis 高级数据类型 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/20230901071808.png +date: 2020-06-24 10:45:38 +order: 02 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 数据类型 +permalink: /pages/518280/ +--- + +# Redis 高级数据类型 + +> 关键词:`BitMap`、`HyperLogLog`、`Geo`、`Stream` + +Redis 支持的高级数据类型:BitMap、HyperLogLog、GEO、Stream + +使用 Redis ,不仅要了解其数据类型的特性,还需要根据业务场景,灵活的、高效的使用其数据类型来建模。 + +## BitMap + +### BitMap 简介 + +Bitmap,**即位图,是一串连续的二进制数组(0 和 1)**,可以通过偏移量(offset)定位元素。由于 bit 是计算机中最小的单位,使用它进行储存将**非常节省空间**,特别适合一些数据量大且使用**二值统计的场景**。例如在一个系统中,不同的用户使用单调递增的用户 ID 表示。40 亿($$2^{32}$$ = $$4*1024*1024*1024$$ ≈ 40 亿)用户只需要 512M 内存就能记住某种状态,例如用户是否已登录。 + +### BitMap 实现 + +实际上,**BitMap 不是真实的数据结构,而是针对 String 实现的一组位操作**。 + +由于 STRING 是二进制安全的,并且其最大长度是 512 MB,所以 BitMap 能最大设置 $$2^{32}$$ 个不同的 bit。 + +### BitMap 命令 + +| 命令 | 行为 | +| -------- | -------------------------------------------------------- | +| `SETBIT` | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit) | +| `GETBIT` | 对 key 所储存的字符串值,获取指定偏移量上的位(bit) | +| `BITOP` | 对一个或多个字符串执行位运算 | + +【示例】SETBIT、GETBIT 操作 + +假设有 1000 个传感器,标记为 0-999。现在,想要快速确定某传感器是否在一小时内对服务器执行了 ping 操作。 + +```shell +# 传感器 123 在 2024 年 1 月 1 日 00:00 内对服务器执行 ping 操作 +> SETBIT pings:2024-01-01-00:00 123 1 +(integer) 0 +# 传感器 123 是否在 2024 年 1 月 1 日 00:00 内对服务器执行 ping 操作 +> GETBIT pings:2024-01-01-00:00 123 +1 +What about sensor 456? +> GETBIT pings:2024-01-01-00:00 456 +0 +``` + +【示例】BITOP 操作 + +```shell +# BitMap间的运算 +# operations 位移操作符,枚举值 + AND 与运算 & + OR 或运算 | + XOR 异或 ^ + NOT 取反 ~ +# result 计算的结果,会存储在该key中 +# key1 … keyn 参与运算的key,可以有多个,空格分割,not运算只能一个key +# 当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0。返回值是保存到 destkey 的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。 +BITOP [operations] [result] [key1] [keyn…] + +# 返回指定key中第一次出现指定value(0/1)的位置 +BITPOS [key] [value] +``` + +### BitMap 应用 + +Bitmap 类型非常适合二值状态统计的场景,这里的二值状态就是指集合元素的取值就只有 0 和 1 两种,在记录海量数据时,Bitmap 能够有效地节省内存空间。 + +#### 签到统计 + +在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。 + +签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。 + +假设我们要统计 ID 100 的用户在 2022 年 6 月份的签到情况,就可以按照下面的步骤进行操作。 + +第一步,执行下面的命令,记录该用户 6 月 3 号已签到。 + +```shell +SETBIT uid:sign:100:202206 2 1 +``` + +第二步,检查该用户 6 月 3 日是否签到。 + +```shell +GETBIT uid:sign:100:202206 2 +``` + +第三步,统计该用户在 6 月份的签到次数。 + +```shell +BITCOUNT uid:sign:100:202206 +``` + +这样,我们就知道该用户在 6 月份的签到情况了。 + +> 如何统计这个月首次打卡时间呢? + +Redis 提供了 `BITPOS key bitValue [start] [end]`指令,返回数据表示 Bitmap 中第一个值为 `bitValue` 的 offset 位置。 + +在默认情况下,命令将检测整个位图,用户可以通过可选的 `start` 参数和 `end` 参数指定要检测的范围。所以我们可以通过执行这条命令来获取 userID = 100 在 2022 年 6 月份**首次打卡**日期: + +```shell +BITPOS uid:sign:100:202206 1 +``` + +需要注意的是,因为 offset 从 0 开始的,所以我们需要将返回的 value + 1。 + +#### 判断用户是否登录 + +Bitmap 提供了 `GETBIT、SETBIT` 操作,通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作,需要注意的是 offset 从 0 开始。 + +只需要一个 key = login_status 表示存储用户登陆状态集合数据,将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 `GETBIT`判断对应的用户是否在线。50000 万 用户只需要 6 MB 的空间。 + +假如我们要判断 ID = 10086 的用户的登陆情况: + +第一步,执行以下指令,表示用户已登录。 + +```shell +SETBIT login_status 10086 1 +``` + +第二步,检查该用户是否登陆,返回值 1 表示已登录。 + +```shell +GETBIT login_status 10086 +``` + +第三步,登出,将 offset 对应的 value 设置成 0。 + +```shell +SETBIT login_status 10086 0 +``` + +#### 连续签到用户总数 + +如何统计出这连续 7 天连续打卡用户总数呢? + +我们把每天的日期作为 Bitmap 的 key,userId 作为 offset,若是打卡则将 offset 位置的 bit 设置成 1。 + +key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。 + +一共有 7 个这样的 Bitmap,如果我们能对这 7 个 Bitmap 的对应的 bit 位做 “与”运算。同样的 UserID offset 都是一样的,当一个 userID 在 7 个 Bitmap 对应对应的 offset 位置的 bit = 1 就说明该用户 7 天连续打卡。 + +结果保存到一个新 Bitmap 中,我们再通过 `BITCOUNT` 统计 bit = 1 的个数便得到了连续打卡 7 天的用户总数了。 + +Redis 提供了 `BITOP operation destkey key [key ...]`这个指令用于对一个或者多个 key 的 Bitmap 进行位元操作。 + +- `operation` 可以是 `and`、`OR`、`NOT`、`XOR`。当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 `0` 。空的 `key` 也被看作是包含 `0` 的字符串序列。 + +假设要统计 3 天连续打卡的用户数,则是将三个 bitmap 进行 AND 操作,并将结果保存到 destmap 中,接着对 destmap 执行 BITCOUNT 统计,如下命令: + +```shell +# 与操作 +BITOP AND destmap bitmap:01 bitmap:02 bitmap:03 +# 统计 bit 位 = 1 的个数 +BITCOUNT destmap +``` + +即使一天产生一个亿的数据,Bitmap 占用的内存也不大,大约占 12 MB 的内存(10^8/8/1024/1024),7 天的 Bitmap 的内存开销约为 84 MB。同时我们最好给 Bitmap 设置过期时间,让 Redis 删除过期的打卡数据,节省内存。 + +## HyperLogLog + +### HyperLogLog 简介 + +Redis HyperLogLog 是 Redis 2.8.9 版本新增的数据类型,是一种用于“统计基数”的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。 + +所以,简单来说 HyperLogLog **提供不精确的去重计数**。 + +HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。 + +在 Redis 里面,**每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 `2^64` 个不同元素的基数**,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。 + +这什么概念?举个例子给大家对比一下。 + +用 Java 语言来说,一般 long 类型占用 8 字节,而 1 字节有 8 位,即:1 byte = 8 bit,即 long 数据类型最大可以表示的数是:`2^63-1`。对应上面的`2^64`个数,假设此时有`2^63-1`这么多个数,从 `0 ~ 2^63-1`,按照`long`以及`1k = 1024 字节`的规则来计算内存总数,就是:`((2^63-1) * 8/1024)K`,这是很庞大的一个数,存储空间远远超过`12K`,而 `HyperLogLog` 却可以用 `12K` 就能统计完。 + +### HyperLogLog 实现 + +HyperLogLog 的实现涉及到很多数学问题,太费脑子了,我也没有搞懂,如果你想了解一下,课下可以看看这个:[HyperLogLog](https://en.wikipedia.org/wiki/HyperLogLog)。 + +### HyperLogLog 命令 + +HyperLogLog 命令很少,就三个。 + +```shell +# 添加指定元素到 HyperLogLog 中 +PFADD key element [element ...] + +# 返回给定 HyperLogLog 的基数估算值。 +PFCOUNT key [key ...] + +# 将多个 HyperLogLog 合并为一个 HyperLogLog +PFMERGE destkey sourcekey [sourcekey ...] +``` + +### HyperLogLog 应用 + +#### 百万级网页 UV 计数 + +Redis HyperLogLog 优势在于只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。 + +所以,非常适合统计百万级以上的网页 UV 的场景。 + +在统计 UV 时,你可以用 PFADD 命令(用于向 HyperLogLog 中添加新元素)把访问页面的每个用户都添加到 HyperLogLog 中。 + +```shell +PFADD page1:uv user1 user2 user3 user4 user5 +``` + +接下来,就可以用 PFCOUNT 命令直接获得 page1 的 UV 值了,这个命令的作用就是返回 HyperLogLog 的统计结果。 + +```shell +PFCOUNT page1:uv +``` + +不过,有一点需要你注意一下,HyperLogLog 的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是 0.81%。 + +这也就意味着,你使用 HyperLogLog 统计的 UV 是 100 万,但实际的 UV 可能是 101 万。虽然误差率不算大,但是,如果你需要精确统计结果的话,最好还是继续用 Set 或 Hash 类型。 + +## GEO + +### GEO 简介 + +Redis GEO 是 Redis 3.2 版本新增的数据类型,主要用于存储地理位置信息,并对存储的信息进行操作。 + +在日常生活中,我们越来越依赖搜索“附近的餐馆”、在打车软件上叫车,这些都离不开基于位置信息服务(Location-Based Service,LBS)的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息,而且要能查询相邻的经纬度范围,GEO 就非常适合应用在 LBS 服务的场景中。 + +### GEO 实现 + +GEO 本身并没有设计新的底层数据结构,而是直接使用了 Zset 类型。 + +GEO 类型使用 GeoHash 编码方法实现了经纬度到 Sorted Set 中元素权重分数的转换,这其中的两个关键机制就是“对二维地图做区间划分”和“对区间进行编码”。一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为 Sorted Set 元素的权重分数。 + +这样一来,我们就可以把经纬度保存到 Sorted Set 中,利用 Sorted Set 提供的“按权重进行有序范围查找”的特性,实现 LBS 服务中频繁使用的“搜索附近”的需求。 + +### GEO 命令 + +```shell +# 存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。 +GEOADD key longitude latitude member [longitude latitude member ...] + +# 从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。 +GEOPOS key member [member ...] + +# 返回两个给定位置之间的距离。 +GEODIST key member1 member2 [m|km|ft|mi] + +# 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。 +GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] +``` + +### GEO 应用 + +#### 滴滴叫车 + +这里以滴滴叫车的场景为例,介绍下具体如何使用 GEO 命令:GEOADD 和 GEORADIUS 这两个命令。 + +假设车辆 ID 是 33,经纬度位置是(116.034579,39.030452),我们可以用一个 GEO 集合保存所有车辆的经纬度,集合 key 是 cars:locations。 + +执行下面的这个命令,就可以把 ID 号为 33 的车辆的当前经纬度位置存入 GEO 集合中: + +```shell +GEOADD cars:locations 116.034579 39.030452 33 +``` + +当用户想要寻找自己附近的网约车时,LBS 应用就可以使用 GEORADIUS 命令。 + +例如,LBS 应用执行下面的命令时,Redis 会根据输入的用户的经纬度信息(116.054579,39.030452),查找以这个经纬度为中心的 5 公里内的车辆信息,并返回给 LBS 应用。 + +```shell +GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10 +``` + +## Stream + +### Stream 简介 + +Redis Stream 是 Redis 5.0 版本新增加的数据类型,Redis 专门为消息队列设计的数据类型。 + +在 Redis 5.0 Stream 没出来之前,消息队列的实现方式都有着各自的缺陷,例如: + +- 发布订阅模式,不能持久化也就无法可靠的保存消息,并且对于离线重连的客户端不能读取历史消息的缺陷; +- List 实现消息队列的方式不能重复消费,一个消息消费完就会被删除,而且生产者需要自行实现全局唯一 ID。 + +基于以上问题,Redis 5.0 便推出了 Stream 类型也是此版本最重要的功能,用于完美地实现消息队列,它支持消息的持久化、支持自动生成全局唯一 ID、支持 ack 确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠。 + +### Stream 命令 + +Stream 消息队列操作命令: + +- XADD:插入消息,保证有序,可以自动生成全局唯一 ID; +- XLEN:查询消息长度; +- XREAD:用于读取消息,可以按 ID 读取数据; +- XDEL:根据消息 ID 删除消息; +- DEL:删除整个 Stream; +- XRANGE:读取区间消息 +- XREADGROUP:按消费组形式读取消息; +- XPENDING 和 XACK: + - XPENDING 命令可以用来查询每个消费组内所有消费者“已读取、但尚未确认”的消息; + - XACK 命令用于向消息队列确认消息处理已完成; + +### Stream 应用 + +#### 消息队列 + +生产者通过 XADD 命令插入一条消息: + +```shell +# * 表示让 Redis 为插入的数据自动生成一个全局唯一的 ID +# 往名称为 mymq 的消息队列中插入一条消息,消息的键是 name,值是 xiaolin +> XADD mymq * name xiaolin +"1654254953808-0" +``` + +插入成功后会返回全局唯一的 ID:"1654254953808-0"。消息的全局唯一 ID 由两部分组成: + +- 第一部分“1654254953808”是数据插入时,以毫秒为单位计算的当前服务器时间; +- 第二部分表示插入消息在当前毫秒内的消息序号,这是从 0 开始编号的。例如,“1654254953808-0”就表示在“1654254953808”毫秒内的第 1 条消息。 + +消费者通过 XREAD 命令从消息队列中读取消息时,可以指定一个消息 ID,并从这个消息 ID 的下一条消息开始进行读取(注意是输入消息 ID 的下一条信息开始读取,不是查询输入 ID 的消息)。 + +```shell +# 从 ID 号为 1654254953807-0 的消息开始,读取后续的所有消息(示例中一共 1 条)。 +> XREAD STREAMS mymq 1654254953807-0 +1) 1) "mymq" + 2) 1) 1) "1654254953808-0" + 2) 1) "name" + 2) "xiaolin" +``` + +如果**想要实现阻塞读(当没有数据时,阻塞住),可以调用 XRAED 时设定 BLOCK 配置项**,实现类似于 BRPOP 的阻塞读取操作。 + +比如,下面这命令,设置了 BLOCK 10000 的配置项,10000 的单位是毫秒,表明 XREAD 在读取最新消息时,如果没有消息到来,XREAD 将阻塞 10000 毫秒(即 10 秒),然后再返回。 + +```shell +# 命令最后的“$”符号表示读取最新的消息 +> XREAD BLOCK 10000 STREAMS mymq $ +(nil) +(10.00s) +``` + +Stream 的基础方法,使用 xadd 存入消息和 xread 循环阻塞读取消息的方式可以实现简易版的消息队列,交互流程如下图所示: + +> 前面介绍的这些操作 List 也支持的,接下来看看 Stream 特有的功能。 + +Stream 可以以使用 **XGROUP 创建消费组**,创建消费组之后,Stream 可以使用 XREADGROUP 命令让消费组内的消费者读取消息。 + +创建两个消费组,这两个消费组消费的消息队列是 mymq,都指定从第一条消息开始读取: + +```shell +# 创建一个名为 group1 的消费组,0-0 表示从第一条消息开始读取。 +> XGROUP CREATE mymq group1 0-0 +OK +# 创建一个名为 group2 的消费组,0-0 表示从第一条消息开始读取。 +> XGROUP CREATE mymq group2 0-0 +OK +``` + +消费组 group1 内的消费者 consumer1 从 mymq 消息队列中读取所有消息的命令如下: + +```shell +# 命令最后的参数“>”,表示从第一条尚未被消费的消息开始读取。 +> XREADGROUP GROUP group1 consumer1 STREAMS mymq > +1) 1) "mymq" + 2) 1) 1) "1654254953808-0" + 2) 1) "name" + 2) "xiaolin" +``` + +**消息队列中的消息一旦被消费组里的一个消费者读取了,就不能再被该消费组内的其他消费者读取了,即同一个消费组里的消费者不能消费同一条消息**。 + +比如说,我们执行完刚才的 XREADGROUP 命令后,再执行一次同样的命令,此时读到的就是空值了: + +```shell +> XREADGROUP GROUP group1 consumer1 STREAMS mymq > +(nil) +``` + +但是,**不同消费组的消费者可以消费同一条消息(但是有前提条件,创建消息组的时候,不同消费组指定了相同位置开始读取消息)**。 + +比如说,刚才 group1 消费组里的 consumer1 消费者消费了一条 id 为 1654254953808-0 的消息,现在用 group2 消费组里的 consumer1 消费者消费消息: + +```shell +> XREADGROUP GROUP group2 consumer1 STREAMS mymq > +1) 1) "mymq" + 2) 1) 1) "1654254953808-0" + 2) 1) "name" + 2) "xiaolin" +``` + +因为我创建两组的消费组都是从第一条消息开始读取,所以可以看到第二组的消费者依然可以消费 id 为 1654254953808-0 的这一条消息。因此,不同的消费组的消费者可以消费同一条消息。 + +使用消费组的目的是让组内的多个消费者共同分担读取消息,所以,我们通常会让每个消费者读取部分消息,从而实现消息读取负载在多个消费者间是均衡分布的。 + +例如,我们执行下列命令,让 group2 中的 consumer1、2、3 各自读取一条消息。 + +```shell +# 让 group2 中的 consumer1 从 mymq 消息队列中消费一条消息 +> XREADGROUP GROUP group2 consumer1 COUNT 1 STREAMS mymq > +1) 1) "mymq" + 2) 1) 1) "1654254953808-0" + 2) 1) "name" + 2) "xiaolin" +# 让 group2 中的 consumer2 从 mymq 消息队列中消费一条消息 +> XREADGROUP GROUP group2 consumer2 COUNT 1 STREAMS mymq > +1) 1) "mymq" + 2) 1) 1) "1654256265584-0" + 2) 1) "name" + 2) "xiaolincoding" +# 让 group2 中的 consumer3 从 mymq 消息队列中消费一条消息 +> XREADGROUP GROUP group2 consumer3 COUNT 1 STREAMS mymq > +1) 1) "mymq" + 2) 1) 1) "1654256271337-0" + 2) 1) "name" + 2) "Tom" +``` + +> 基于 Stream 实现的消息队列,如何保证消费者在发生故障或宕机再次重启后,仍然可以读取未处理完的消息? + +Streams 会自动使用内部队列(也称为 PENDING List)留存消费组里每个消费者读取的消息,直到消费者使用 XACK 命令通知 Streams“消息已经处理完成”。 + +消费确认增加了消息的可靠性,一般在业务处理完成之后,需要执行 XACK 命令确认消息已经被消费完成,整个流程的执行如下图所示: + +如果消费者没有成功处理消息,它就不会给 Streams 发送 XACK 命令,消息仍然会留存。此时,**消费者可以在重启后,用 XPENDING 命令查看已读取、但尚未确认处理完成的消息**。 + +例如,我们来查看一下 group2 中各个消费者已读取、但尚未确认的消息个数,命令如下: + +```shell +127.0.0.1:6379> XPENDING mymq group2 +1) (integer) 3 +2) "1654254953808-0" # 表示 group2 中所有消费者读取的消息最小 ID +3) "1654256271337-0" # 表示 group2 中所有消费者读取的消息最大 ID +4) 1) 1) "consumer1" + 2) "1" + 2) 1) "consumer2" + 2) "1" + 3) 1) "consumer3" + 2) "1" +``` + +如果想查看某个消费者具体读取了哪些数据,可以执行下面的命令: + +```shell +# 查看 group2 里 consumer2 已从 mymq 消息队列中读取了哪些消息 +> XPENDING mymq group2 - + 10 consumer2 +1) 1) "1654256265584-0" + 2) "consumer2" + 3) (integer) 410700 + 4) (integer) 1 +``` + +可以看到,consumer2 已读取的消息的 ID 是 1654256265584-0。 + +**一旦消息 1654256265584-0 被 consumer2 处理了,consumer2 就可以使用 XACK 命令通知 Streams,然后这条消息就会被删除**。 + +```shell +> XACK mymq group2 1654256265584-0 +(integer) 1 +``` + +当我们再使用 XPENDING 命令查看时,就可以看到,consumer2 已经没有已读取、但尚未确认处理的消息了。 + +```shell +> XPENDING mymq group2 - + 10 consumer2 +(empty array) +``` + +好了,基于 Stream 实现的消息队列就说到这里了,小结一下: + +- 消息保序:XADD/XREAD +- 阻塞读取:XREAD block +- 重复消息处理:Stream 在使用 XADD 命令,会自动生成全局唯一 ID; +- 消息可靠性:内部使用 PENDING List 自动保存消息,使用 XPENDING 命令查看消费组已经读取但是未被确认的消息,消费者使用 XACK 确认消息; +- 支持消费组形式消费数据 + +> Redis 基于 Stream 消息队列与专业的消息队列有哪些差距? + +一个专业的消息队列,必须要做到两大块: + +- 消息不丢。 +- 消息可堆积。 + +_1、Redis Stream 消息会丢失吗?_ + +使用一个消息队列,其实就分为三大块:**生产者、队列中间件、消费者**,所以要保证消息就是保证三个环节都不能丢失数据。 + +Redis Stream 消息队列能不能保证三个环节都不丢失数据? + +- Redis 生产者会不会丢消息?生产者会不会丢消息,取决于生产者对于异常情况的处理是否合理。从消息被生产出来,然后提交给 MQ 的过程中,只要能正常收到(MQ 中间件)的 ack 确认响应,就表示发送成功,所以只要处理好返回值和异常,如果返回异常则进行消息重发,那么这个阶段是不会出现消息丢失的。 +- Redis 消费者会不会丢消息?不会,因为 Stream(MQ 中间件)会自动使用内部队列(也称为 PENDING List)留存消费组里每个消费者读取的消息,但是未被确认的消息。消费者可以在重启后,用 XPENDING 命令查看已读取、但尚未确认处理完成的消息。等到消费者执行完业务逻辑后,再发送消费确认 XACK 命令,也能保证消息的不丢失。 +- Redis 消息中间件会不会丢消息?**会**,Redis 在以下 2 个场景下,都会导致数据丢失: + - AOF 持久化配置为每秒写盘,但这个写盘过程是异步的,Redis 宕机时会存在数据丢失的可能 + - 主从复制也是异步的,[主从切换时,也存在丢失数据的可能](https://xiaolincoding.com/redis/cluster/master_slave_replication.html#redis-%E4%B8%BB%E4%BB%8E%E5%88%87%E6%8D%A2%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91%E6%95%B0%E6%8D%AE%E4%B8%A2%E5%A4%B1)。 + +可以看到,Redis 在队列中间件环节无法保证消息不丢。像 RabbitMQ 或 Kafka 这类专业的队列中间件,在使用时是部署一个集群,生产者在发布消息时,队列中间件通常会写“多个节点”,也就是有多个副本,这样一来,即便其中一个节点挂了,也能保证集群的数据不丢失。 + +_2、Redis Stream 消息可堆积吗?_ + +Redis 的数据都存储在内存中,这就意味着一旦发生消息积压,则会导致 Redis 的内存持续增长,如果超过机器内存上限,就会面临被 OOM 的风险。 + +所以 Redis 的 Stream 提供了可以指定队列最大长度的功能,就是为了避免这种情况发生。 + +当指定队列最大长度时,队列长度超过上限后,旧消息会被删除,只保留固定长度的新消息。这么来看,Stream 在消息积压时,如果指定了最大长度,还是有可能丢失消息的。 + +但 Kafka、RabbitMQ 专业的消息队列它们的数据都是存储在磁盘上,当消息积压时,无非就是多占用一些磁盘空间。 + +因此,把 Redis 当作队列来使用时,会面临的 2 个问题: + +- Redis 本身可能会丢数据; +- 面对消息挤压,内存资源会紧张; + +所以,能不能将 Redis 作为消息队列来使用,关键看你的业务场景: + +- 如果你的业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把 Redis 当作队列是完全可以的。 +- 如果你的业务有海量消息,消息积压的概率比较大,并且不能接受数据丢失,那么还是用专业的消息队列中间件吧。 + +> 补充:Redis 发布/订阅机制为什么不可以作为消息队列? + +发布订阅机制存在以下缺点,都是跟丢失数据有关: + +1. 发布/订阅机制没有基于任何数据类型实现,所以不具备“数据持久化”的能力,也就是发布/订阅机制的相关操作,不会写入到 RDB 和 AOF 中,当 Redis 宕机重启,发布/订阅机制的数据也会全部丢失。 +2. 发布订阅模式是“发后既忘”的工作模式,如果有订阅者离线重连之后不能消费之前的历史消息。 +3. 当消费端有一定的消息积压时,也就是生产者发送的消息,消费者消费不过来时,如果超过 32M 或者是 60s 内持续保持在 8M 以上,消费端会被强行断开,这个参数是在配置文件中设置的,默认值是 `client-output-buffer-limit pubsub 32mb 8mb 60`。 + +所以,发布/订阅机制只适合即时通讯的场景,比如[构建哨兵集群](https://xiaolincoding.com/redis/cluster/sentinel.html#%E5%93%A8%E5%85%B5%E9%9B%86%E7%BE%A4%E6%98%AF%E5%A6%82%E4%BD%95%E7%BB%84%E6%88%90%E7%9A%84)的场景采用了发布/订阅机制。 + +## 总结 + +Redis 后续版本又支持四种数据类型,它们的应用场景如下: + +- BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等; +- HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等; +- GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车; +- Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息 ID,支持以消费组形式消费数据。 + +针对 Redis 是否适合做消息队列,关键看你的业务场景: + +- 如果你的业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把 Redis 当作队列是完全可以的。 +- 如果你的业务有海量消息,消息积压的概率比较大,并且不能接受数据丢失,那么还是用专业的消息队列中间件吧。 + +## 参考资料 + +- [Redis 官网](https://redis.io/) +- [Redis 官方文档中文版](http://redis.cn/) +- [《Redis 实战》](https://item.jd.com/11791607.html) +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) +- [一看就懂系列之 详解 redis 的 bitmap 在亿级项目中的应用](https://blog.csdn.net/u011957758/article/details/74783347) +- [Fast, easy, realtime metrics using Redis BitMap](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-BitMap/) +- [Redis 常见数据类型和应用场景](https://xiaolincoding.com/redis/data_struct/command.html#string) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" deleted file mode 100644 index ccc9e04446..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\261\273\345\236\213\345\222\214\345\272\224\347\224\250.md" +++ /dev/null @@ -1,1187 +0,0 @@ ---- -title: Redis 数据类型和应用 -date: 2020-06-24 10:45:38 -categories: - - 数据库 - - KV数据库 - - Redis -tags: - - 数据库 - - KV数据库 - - Redis - - 数据类型 -permalink: /pages/ed757c/ ---- - -# Redis 数据类型和应用 - -> Redis 提供了多种数据类型,每种数据类型有丰富的命令支持。 -> -> 使用 Redis ,不仅要了解其数据类型的特性,还需要根据业务场景,灵活的、高效的使用其数据类型来建模。 - -## 一、Redis 基本数据类型 - -![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200226113813.png) - -| 数据类型 | 可以存储的值 | 操作 | -| -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | -| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作
对整数和浮点数执行自增或者自减操作 | -| LIST | 列表 | 从两端压入或者弹出元素
读取单个或者多个元素
进行修剪,只保留一个范围内的元素 | -| SET | 无序集合 | 添加、获取、移除单个元素
检查一个元素是否存在于集合中
计算交集、并集、差集
从集合里面随机获取元素 | -| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对
获取所有键值对
检查某个键是否存在 | -| ZSET | 有序集合 | 添加、获取、删除元素
根据分值范围或者成员来获取元素
计算一个键的排名 | - -> [What Redis data structures look like](https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/) - -### STRING - -
- -
-**适用场景:缓存、计数器、共享 Session** - -命令: - -| 命令 | 行为 | -| ------ | ---------------------------------------------------- | -| `GET` | 获取存储在给定键中的值。 | -| `SET` | 设置存储在给定键中的值。 | -| `DEL` | 删除存储在给定键中的值(这个命令可以用于所有类型)。 | -| `INCR` | 为键 `key` 储存的数字值加一 | -| `DECR` | 为键 `key` 储存的数字值减一 | - -> 更多命令请参考:[Redis String 类型命令](https://redis.io/commands#string) - -示例: - -```shell -127.0.0.1:6379> set hello world -OK -127.0.0.1:6379> get hello -"world" -127.0.0.1:6379> del hello -(integer) 1 -127.0.0.1:6379> get hello -(nil) -``` - -### HASH - -
- -
-**适用场景:存储结构化数据**,如一个对象:用户信息、产品信息等。 - -命令: - -| 命令 | 行为 | -| --------- | ------------------------------------------ | -| `HSET` | 在散列里面关联起给定的键值对。 | -| `HGET` | 获取指定散列键的值。 | -| `HGETALL` | 获取散列包含的所有键值对。 | -| `HDEL` | 如果给定键存在于散列里面,那么移除这个键。 | - -> 更多命令请参考:[Redis Hash 类型命令](https://redis.io/commands#hash) - -示例: - -```shell -127.0.0.1:6379> hset hash-key sub-key1 value1 -(integer) 1 -127.0.0.1:6379> hset hash-key sub-key2 value2 -(integer) 1 -127.0.0.1:6379> hset hash-key sub-key1 value1 -(integer) 0 -127.0.0.1:6379> hset hash-key sub-key3 value2 -(integer) 0 -127.0.0.1:6379> hgetall hash-key -1) "sub-key1" -2) "value1" -3) "sub-key2" -4) "value2" -127.0.0.1:6379> hdel hash-key sub-key2 -(integer) 1 -127.0.0.1:6379> hdel hash-key sub-key2 -(integer) 0 -127.0.0.1:6379> hget hash-key sub-key1 -"value1" -127.0.0.1:6379> hgetall hash-key -1) "sub-key1" -2) "value1" -``` - -### LIST - -
- -
-**适用场景:用于存储列表型数据**。如:粉丝列表、商品列表等。 - -命令: - -| 命令 | 行为 | -| -------- | ------------------------------------------ | -| `LPUSH` | 将给定值推入列表的右端。 | -| `RPUSH` | 将给定值推入列表的右端。 | -| `LPOP` | 从列表的左端弹出一个值,并返回被弹出的值。 | -| `RPOP` | 从列表的右端弹出一个值,并返回被弹出的值。 | -| `LRANGE` | 获取列表在给定范围上的所有值。 | -| `LINDEX` | 获取列表在给定位置上的单个元素。 | -| `LREM` | 从列表的左端弹出一个值,并返回被弹出的值。 | -| `LTRIM` | 只保留指定区间内的元素,删除其他元素。 | - -> 更多命令请参考:[Redis List 类型命令](https://redis.io/commands#list) - -示例: - -```shell -127.0.0.1:6379> rpush list-key item -(integer) 1 -127.0.0.1:6379> rpush list-key item2 -(integer) 2 -127.0.0.1:6379> rpush list-key item -(integer) 3 -127.0.0.1:6379> lrange list-key 0 -1 -1) "item" -2) "item2" -3) "item" -127.0.0.1:6379> lindex list-key 1 -"item2" -127.0.0.1:6379> lpop list-key -"item" -127.0.0.1:6379> lrange list-key 0 -1 -1) "item2" -2) "item" -``` - -### SET - -
- -
-**适用场景:用于存储去重的列表型数据**。 - -命令: - -| 命令 | 行为 | -| ----------- | ---------------------------------------------- | -| `SADD` | 将给定元素添加到集合。 | -| `SMEMBERS` | 返回集合包含的所有元素。 | -| `SISMEMBER` | 检查给定元素是否存在于集合中。 | -| `SREM` | 如果给定的元素存在于集合中,那么移除这个元素。 | - -> 更多命令请参考:[Redis Set 类型命令](https://redis.io/commands#set) - -示例: - -```shell -127.0.0.1:6379> sadd set-key item -(integer) 1 -127.0.0.1:6379> sadd set-key item2 -(integer) 1 -127.0.0.1:6379> sadd set-key item3 -(integer) 1 -127.0.0.1:6379> sadd set-key item -(integer) 0 -127.0.0.1:6379> smembers set-key -1) "item" -2) "item2" -3) "item3" -127.0.0.1:6379> sismember set-key item4 -(integer) 0 -127.0.0.1:6379> sismember set-key item -(integer) 1 -127.0.0.1:6379> srem set-key item2 -(integer) 1 -127.0.0.1:6379> srem set-key item2 -(integer) 0 -127.0.0.1:6379> smembers set-key -1) "item" -2) "item3" -``` - -### ZSET - -
- -
- -适用场景:由于可以设置 score,且不重复。**适合用于存储各种排行数据**,如:按评分排序的有序商品集合、按时间排序的有序文章集合。 - -命令: - -| 命令 | 行为 | -| --------------- | ------------------------------------------------------------ | -| `ZADD` | 将一个带有给定分值的成员添加到有序集合里面。 | -| `ZRANGE` | 根据元素在有序排列中所处的位置,从有序集合里面获取多个元素。 | -| `ZRANGEBYSCORE` | 获取有序集合在给定分值范围内的所有元素。 | -| `ZREM` | 如果给定成员存在于有序集合,那么移除这个成员。 | - -> 更多命令请参考:[Redis ZSet 类型命令](https://redis.io/commands#sorted_set) - -示例: - -```shell -127.0.0.1:6379> zadd zset-key 728 member1 -(integer) 1 -127.0.0.1:6379> zadd zset-key 982 member0 -(integer) 1 -127.0.0.1:6379> zadd zset-key 982 member0 -(integer) 0 - -127.0.0.1:6379> zrange zset-key 0 -1 withscores -1) "member1" -2) "728" -3) "member0" -4) "982" - -127.0.0.1:6379> zrangebyscore zset-key 0 800 withscores -1) "member1" -2) "728" - -127.0.0.1:6379> zrem zset-key member1 -(integer) 1 -127.0.0.1:6379> zrem zset-key member1 -(integer) 0 -127.0.0.1:6379> zrange zset-key 0 -1 withscores -1) "member0" -2) "982" -``` - -### 通用命令 - -#### 排序 - -Redis 的 `SORT` 命令可以对 `LIST`、`SET`、`ZSET` 进行排序。 - -| 命令 | 描述 | -| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `SORT` | `SORT source-key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE dest-key]`—根据给定选项,对输入 `LIST`、`SET`、`ZSET` 进行排序,然后返回或存储排序的结果。 | - -示例: - -```shell -127.0.0.1:6379[15]> RPUSH 'sort-input' 23 15 110 7 -(integer) 4 -127.0.0.1:6379[15]> SORT 'sort-input' -1) "7" -2) "15" -3) "23" -4) "110" -127.0.0.1:6379[15]> SORT 'sort-input' alpha -1) "110" -2) "15" -3) "23" -4) "7" -127.0.0.1:6379[15]> HSET 'd-7' 'field' 5 -(integer) 1 -127.0.0.1:6379[15]> HSET 'd-15' 'field' 1 -(integer) 1 -127.0.0.1:6379[15]> HSET 'd-23' 'field' 9 -(integer) 1 -127.0.0.1:6379[15]> HSET 'd-110' 'field' 3 -(integer) 1 -127.0.0.1:6379[15]> SORT 'sort-input' by 'd-*->field' -1) "15" -2) "110" -3) "7" -4) "23" -127.0.0.1:6379[15]> SORT 'sort-input' by 'd-*->field' get 'd-*->field' -1) "1" -2) "3" -3) "5" -4) "9" -``` - -#### 键的过期时间 - -Redis 的 `EXPIRE` 命令可以指定一个键的过期时间,当达到过期时间后,Redis 会自动删除该键。 - -| 命令 | 描述 | -| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `PERSIST` | `PERSIST key-name`—移除键的过期时间 | -| `TTL` | `TTL key-name`—查看给定键距离过期还有多少秒 | -| `EXPIRE` | `EXPIRE key-name seconds`—让给定键在指定的秒数之后过期 | -| `EXPIREAT` | `EXPIREAT key-name timestamp`—将给定键的过期时间设置为给定的 UNIX 时间戳 | -| `PTTL` | `PTTL key-name`—查看给定键距离过期时间还有多少毫秒(这个命令在 Redis 2.6 或以上版本可用) | -| `PEXPIRE` | `PEXPIRE key-name milliseconds`—让给定键在指定的毫秒数之后过期(这个命令在 Redis 2.6 或以上版本可用) | -| `PEXPIREAT` | `PEXPIREAT key-name timestamp-milliseconds`—将一个毫秒级精度的 UNIX 时间戳设置为给定键的过期时间(这个命令在 Redis 2.6 或以上版本可用) | - -示例: - -```shell -127.0.0.1:6379[15]> SET key value -OK -127.0.0.1:6379[15]> GET key -"value" -127.0.0.1:6379[15]> EXPIRE key 2 -(integer) 1 -127.0.0.1:6379[15]> GET key -(nil) -``` - -## 二、Redis 高级数据类型 - -### BitMap - -BitMap 即位图。BitMap 不是一个真实的数据结构。而是 STRING 类型上的一组面向 bit 操作的集合。由于 STRING 是二进制安全的 blob,并且它们的最大长度是 512m,所以 BitMap 能最大设置 $$2^{32}$$ 个不同的 bit。 - -Bitmaps 的最大优点就是存储信息时可以节省大量的空间。例如在一个系统中,不同的用户被一个增长的用户 ID 表示。40 亿($$2^{32}$$ = $$4*1024*1024*1024$$ ≈ 40 亿)用户只需要 512M 内存就能记住某种信息,例如用户是否登录过。 - -#### BitMap 命令 - -- [SETBIT](http://redisdoc.com/bitmap/setbit.html) - 对 `key` 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 -- [GETBIT](http://redisdoc.com/bitmap/getbit.html) - 对 `key` 所储存的字符串值,获取指定偏移量上的位(bit)。 -- [BITCOUNT](http://redisdoc.com/bitmap/bitcount.html) - 计算给定字符串中,被设置为 `1` 的比特位的数量。 -- [BITPOS](http://redisdoc.com/bitmap/bitpos.html) -- [BITOP](http://redisdoc.com/bitmap/bitop.html) -- [BITFIELD](http://redisdoc.com/bitmap/bitfield.html) - -#### BitMap 示例 - -```shell -# 对不存在的 key 或者不存在的 offset 进行 GETBIT, 返回 0 - -redis> EXISTS bit -(integer) 0 - -redis> GETBIT bit 10086 -(integer) 0 - - -# 对已存在的 offset 进行 GETBIT - -redis> SETBIT bit 10086 1 -(integer) 0 - -redis> GETBIT bit 10086 -(integer) 1 - -redis> BITCOUNT bit -(integer) 1 -``` - -#### BitMap 应用 - -Bitmap 对于一些特定类型的计算非常有效。例如:使用 bitmap 实现用户上线次数统计。 - -假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 [SETBIT key offset value](http://redisdoc.com/bitmap/setbit.html#setbit) 和 [BITCOUNT key [start\] [end]](http://redisdoc.com/bitmap/bitcount.html#bitcount) 来实现。 - -比如说,每当用户在某一天上线的时候,我们就使用 [SETBIT key offset value](http://redisdoc.com/bitmap/setbit.html#setbit) ,以用户名作为 `key`,将那天所代表的网站的上线日作为 `offset` 参数,并将这个 `offset` 上的为设置为 `1` 。 - -> 更详细的实现可以参考: -> -> [一看就懂系列之 详解 redis 的 bitmap 在亿级项目中的应用](https://blog.csdn.net/u011957758/article/details/74783347) -> -> [Fast, easy, realtime metrics using Redis bitmaps](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/) - -### HyperLogLog - -HyperLogLog 是用于计算唯一事物的概率数据结构(从技术上讲,这被称为估计集合的基数)。如果统计唯一项,项目越多,需要的内存就越多。因为需要记住过去已经看过的项,从而避免多次统计这些项。 - -#### HyperLogLog 命令 - -- [PFADD](http://redisdoc.com/hyperloglog/pfadd.html) - 将任意数量的元素添加到指定的 HyperLogLog 里面。 -- [PFCOUNT](http://redisdoc.com/hyperloglog/pfcount.html) - 返回 HyperLogLog 包含的唯一元素的近似数量。 -- [PFMERGE](http://redisdoc.com/hyperloglog/pfmerge.html) - 将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集。合并得出的 HyperLogLog 会被储存在 `destkey` 键里面, 如果该键并不存在, 那么命令在执行之前, 会先为该键创建一个空的 HyperLogLog 。 - -示例: - -```shell -redis> PFADD databases "Redis" "MongoDB" "MySQL" -(integer) 1 - -redis> PFCOUNT databases -(integer) 3 - -redis> PFADD databases "Redis" # Redis 已经存在,不必对估计数量进行更新 -(integer) 0 - -redis> PFCOUNT databases # 元素估计数量没有变化 -(integer) 3 - -redis> PFADD databases "PostgreSQL" # 添加一个不存在的元素 -(integer) 1 - -redis> PFCOUNT databases # 估计数量增一 -4 -``` - -### GEO - -这个功能可以将用户给定的地理位置(经度和纬度)信息储存起来,并对这些信息进行操作。 - -#### GEO 命令 - -- [GEOADD](http://redisdoc.com/geo/geoadd.html) - 将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中。 -- [GEOPOS](http://redisdoc.com/geo/geopos.html) - 从 key 里返回所有给定位置元素的位置(经度和纬度)。 -- [GEODIST](http://redisdoc.com/geo/geodist.html) - 返回两个给定位置之间的距离。 -- [GEOHASH](http://redisdoc.com/geo/geohash.html) - 回一个或多个位置元素的标准 Geohash 值,它可以在http://geohash.org/使用。 -- [GEORADIUS](http://redisdoc.com/geo/georadius.html) -- [GEORADIUSBYMEMBER](http://redisdoc.com/geo/georadiusbymember.html) - -## 三、Redis 数据类型应用 - -### 案例-最受欢迎文章 - -选出最受欢迎文章,需要支持对文章进行评分。 - -#### 对文章进行投票 - -(1)使用 HASH 存储文章 - -使用 `HASH` 类型存储文章信息。其中:key 是文章 ID;field 是文章的属性 key;value 是属性对应值。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225143038.jpg) - -操作: - -- 存储文章信息 - 使用 `HSET` 或 `HMGET` 命令 -- 查询文章信息 - 使用 `HGETALL` 命令 -- 添加投票 - 使用 `HINCRBY` 命令 - -(2)使用 `ZSET` 针对不同维度集合排序 - -使用 `ZSET` 类型分别存储按照时间排序和按照评分排序的文章 ID 集合。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225145742.jpg) - -操作: - -- 添加记录 - 使用 `ZADD` 命令 -- 添加分数 - 使用 `ZINCRBY` 命令 -- 取出多篇文章 - 使用 `ZREVRANGE` 命令 - -(3)为了防止重复投票,使用 `SET` 类型记录每篇文章 ID 对应的投票集合。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225150105.jpg) - -操作: - -- 添加投票者 - 使用 `SADD` 命令 -- 设置有效期 - 使用 `EXPIRE` 命令 - -(4)假设 user:115423 给 article:100408 投票,分别需要高更新评分排序集合以及投票集合。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225150138.jpg) - -当需要对一篇文章投票时,程序需要用 ZSCORE 命令检查记录文章发布时间的有序集合,判断文章的发布时间是否超过投票有效期(比如:一星期)。 - -```java - public void articleVote(Jedis conn, String user, String article) { - // 计算文章的投票截止时间。 - long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS; - - // 检查是否还可以对文章进行投票 - // (虽然使用散列也可以获取文章的发布时间, - // 但有序集合返回的文章发布时间为浮点数, - // 可以不进行转换直接使用)。 - if (conn.zscore("time:", article) < cutoff) { - return; - } - - // 从article:id标识符(identifier)里面取出文章的ID。 - String articleId = article.substring(article.indexOf(':') + 1); - - // 如果用户是第一次为这篇文章投票,那么增加这篇文章的投票数量和评分。 - if (conn.sadd("voted:" + articleId, user) == 1) { - conn.zincrby("score:", VOTE_SCORE, article); - conn.hincrBy(article, "votes", 1); - } - } -``` - -#### 发布并获取文章 - -发布文章: - -- 添加文章 - 使用 `INCR` 命令计算新的文章 ID,填充文章信息,然后用 `HSET` 命令或 `HMSET` 命令写入到 `HASH` 结构中。 -- 将文章作者 ID 添加到投票名单 - 使用 `SADD` 命令添加到代表投票名单的 `SET` 结构中。 -- 设置投票有效期 - 使用 `EXPIRE` 命令设置投票有效期。 - -```java - public String postArticle(Jedis conn, String user, String title, String link) { - // 生成一个新的文章ID。 - String articleId = String.valueOf(conn.incr("article:")); - - String voted = "voted:" + articleId; - // 将发布文章的用户添加到文章的已投票用户名单里面, - conn.sadd(voted, user); - // 然后将这个名单的过期时间设置为一周(第3章将对过期时间作更详细的介绍)。 - conn.expire(voted, ONE_WEEK_IN_SECONDS); - - long now = System.currentTimeMillis() / 1000; - String article = "article:" + articleId; - // 将文章信息存储到一个散列里面。 - HashMap articleData = new HashMap(); - articleData.put("title", title); - articleData.put("link", link); - articleData.put("user", user); - articleData.put("now", String.valueOf(now)); - articleData.put("votes", "1"); - conn.hmset(article, articleData); - - // 将文章添加到根据发布时间排序的有序集合和根据评分排序的有序集合里面。 - conn.zadd("score:", now + VOTE_SCORE, article); - conn.zadd("time:", now, article); - - return articleId; - } -``` - -分页查询最受欢迎文章: - -使用 `ZINTERSTORE` 命令根据页码、每页记录数、排序号,根据评分值从大到小分页查出文章 ID 列表。 - -```java - public List> getArticles(Jedis conn, int page, String order) { - // 设置获取文章的起始索引和结束索引。 - int start = (page - 1) * ARTICLES_PER_PAGE; - int end = start + ARTICLES_PER_PAGE - 1; - - // 获取多个文章ID。 - Set ids = conn.zrevrange(order, start, end); - List> articles = new ArrayList<>(); - // 根据文章ID获取文章的详细信息。 - for (String id : ids) { - Map articleData = conn.hgetAll(id); - articleData.put("id", id); - articles.add(articleData); - } - - return articles; - } -``` - -#### 对文章进行分组 - -如果文章需要分组,功能需要分为两块: - -- 记录文章属于哪个群组 -- 负责取出群组里的文章 - -将文章添加、删除群组: - -```java - public void addRemoveGroups(Jedis conn, String articleId, String[] toAdd, String[] toRemove) { - // 构建存储文章信息的键名。 - String article = "article:" + articleId; - // 将文章添加到它所属的群组里面。 - for (String group : toAdd) { - conn.sadd("group:" + group, article); - } - // 从群组里面移除文章。 - for (String group : toRemove) { - conn.srem("group:" + group, article); - } - } -``` - -取出群组里的文章: - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200225214210.jpg) - -- 通过对存储群组文章的集合和存储文章评分的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章评分排序的群组文章。 -- 通过对存储群组文章的集合和存储文章发布时间的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章发布时间排序的群组文章。 - -```java - public List> getGroupArticles(Jedis conn, String group, int page, String order) { - // 为每个群组的每种排列顺序都创建一个键。 - String key = order + group; - // 检查是否有已缓存的排序结果,如果没有的话就现在进行排序。 - if (!conn.exists(key)) { - // 根据评分或者发布时间,对群组文章进行排序。 - ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX); - conn.zinterstore(key, params, "group:" + group, order); - // 让Redis在60秒钟之后自动删除这个有序集合。 - conn.expire(key, 60); - } - // 调用之前定义的getArticles函数来进行分页并获取文章数据。 - return getArticles(conn, page, key); - } -``` - -### 案例-管理令牌 - -网站一般会以 Cookie、Session、令牌这类信息存储用户身份信息。 - -可以将 Cookie/Session/令牌 和用户的映射关系存储在 `HASH` 结构。 - -下面以令牌来举例。 - -#### 查询令牌 - -```java - public String checkToken(Jedis conn, String token) { - // 尝试获取并返回令牌对应的用户。 - return conn.hget("login:", token); - } -``` - -#### 更新令牌 - -- 用户每次访问页面,可以记录下令牌和当前时间戳的映射关系,存入一个 `ZSET` 结构中,以便分析用户是否活跃,继而可以周期性清理最老的令牌,统计当前在线用户数等行为。 -- 用户如果正在浏览商品,可以记录到用户最近浏览过的商品有序集合中(集合可以限定数量,超过数量进行裁剪),存入到一个 `ZSET` 结构中,以便分析用户最近可能感兴趣的商品,以便推荐商品。 - -```java - public void updateToken(Jedis conn, String token, String user, String item) { - // 获取当前时间戳。 - long timestamp = System.currentTimeMillis() / 1000; - // 维持令牌与已登录用户之间的映射。 - conn.hset("login:", token, user); - // 记录令牌最后一次出现的时间。 - conn.zadd("recent:", timestamp, token); - if (item != null) { - // 记录用户浏览过的商品。 - conn.zadd("viewed:" + token, timestamp, item); - // 移除旧的记录,只保留用户最近浏览过的25个商品。 - conn.zremrangeByRank("viewed:" + token, 0, -26); - conn.zincrby("viewed:", -1, item); - } - } -``` - -#### 清理令牌 - -上一节提到,更新令牌时,将令牌和当前时间戳的映射关系,存入一个 `ZSET` 结构中。所以可以通过排序得知哪些令牌最老。如果没有清理操作,更新令牌占用的内存会不断膨胀,直到导致机器宕机。 - -比如:最多允许存储 1000 万条令牌信息,周期性检查,一旦发现记录数超出 1000 万条,将 ZSET 从新到老排序,将超出 1000 万条的记录清除。 - -```java -public static class CleanSessionsThread extends Thread { - - private Jedis conn; - - private int limit; - - private volatile boolean quit; - - public CleanSessionsThread(int limit) { - this.conn = new Jedis("localhost"); - this.conn.select(15); - this.limit = limit; - } - - public void quit() { - quit = true; - } - - @Override - public void run() { - while (!quit) { - // 找出目前已有令牌的数量。 - long size = conn.zcard("recent:"); - // 令牌数量未超过限制,休眠并在之后重新检查。 - if (size <= limit) { - try { - sleep(1000); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - } - continue; - } - - // 获取需要移除的令牌ID。 - long endIndex = Math.min(size - limit, 100); - Set tokenSet = conn.zrange("recent:", 0, endIndex - 1); - String[] tokens = tokenSet.toArray(new String[tokenSet.size()]); - - // 为那些将要被删除的令牌构建键名。 - ArrayList sessionKeys = new ArrayList(); - for (String token : tokens) { - sessionKeys.add("viewed:" + token); - } - - // 移除最旧的那些令牌。 - conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); - conn.hdel("login:", tokens); - conn.zrem("recent:", tokens); - } - } - -} -``` - -### 案例-购物车 - -可以使用 HASH 结构来实现购物车功能。 - -每个用户的购物车,存储了商品 ID 和商品数量的映射。 - -#### 在购物车中添加、删除商品 - -```java - public void addToCart(Jedis conn, String session, String item, int count) { - if (count <= 0) { - // 从购物车里面移除指定的商品。 - conn.hdel("cart:" + session, item); - } else { - // 将指定的商品添加到购物车。 - conn.hset("cart:" + session, item, String.valueOf(count)); - } - } -``` - -#### 清空购物车 - -在 [清理令牌](#清理令牌) 的基础上,清空会话时,顺便将购物车缓存一并清理。 - -```java - while (!quit) { - long size = conn.zcard("recent:"); - if (size <= limit) { - try { - sleep(1000); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - } - continue; - } - - long endIndex = Math.min(size - limit, 100); - Set sessionSet = conn.zrange("recent:", 0, endIndex - 1); - String[] sessions = sessionSet.toArray(new String[sessionSet.size()]); - - ArrayList sessionKeys = new ArrayList(); - for (String sess : sessions) { - sessionKeys.add("viewed:" + sess); - // 新增加的这行代码用于删除旧会话对应用户的购物车。 - sessionKeys.add("cart:" + sess); - } - - conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); - conn.hdel("login:", sessions); - conn.zrem("recent:", sessions); - } -``` - -### 案例-页面缓存 - -大部分网页内容并不会经常改变,但是访问时,后台需要动态计算,这可能耗时较多,此时可以使用 `STRING` 结构存储页面缓存, - -```java - public String cacheRequest(Jedis conn, String request, Callback callback) { - // 对于不能被缓存的请求,直接调用回调函数。 - if (!canCache(conn, request)) { - return callback != null ? callback.call(request) : null; - } - - // 将请求转换成一个简单的字符串键,方便之后进行查找。 - String pageKey = "cache:" + hashRequest(request); - // 尝试查找被缓存的页面。 - String content = conn.get(pageKey); - - if (content == null && callback != null) { - // 如果页面还没有被缓存,那么生成页面。 - content = callback.call(request); - // 将新生成的页面放到缓存里面。 - conn.setex(pageKey, 300, content); - } - - // 返回页面。 - return content; - } -``` - -### 案例-数据行缓存 - -电商网站可能会有促销、特卖、抽奖等活动,这些活动页面只需要从数据库中加载几行数据,如:用户信息、商品信息。 - -可以使用 `STRING` 结构来缓存这些数据,使用 JSON 存储结构化的信息。 - -此外,需要有两个 `ZSET` 结构来记录更新缓存的时机: - -- 第一个为调度有序集合; -- 第二个为延时有序集合。 - -记录缓存时机: - -```java - public void scheduleRowCache(Jedis conn, String rowId, int delay) { - // 先设置数据行的延迟值。 - conn.zadd("delay:", delay, rowId); - // 立即缓存数据行。 - conn.zadd("schedule:", System.currentTimeMillis() / 1000, rowId); - } -``` - -定时更新数据行缓存: - -```java -public class CacheRowsThread extends Thread { - - private Jedis conn; - - private boolean quit; - - public CacheRowsThread() { - this.conn = new Jedis("localhost"); - this.conn.select(15); - } - - public void quit() { - quit = true; - } - - @Override - public void run() { - Gson gson = new Gson(); - while (!quit) { - // 尝试获取下一个需要被缓存的数据行以及该行的调度时间戳, - // 命令会返回一个包含零个或一个元组(tuple)的列表。 - Set range = conn.zrangeWithScores("schedule:", 0, 0); - Tuple next = range.size() > 0 ? range.iterator().next() : null; - long now = System.currentTimeMillis() / 1000; - if (next == null || next.getScore() > now) { - try { - // 暂时没有行需要被缓存,休眠50毫秒后重试。 - sleep(50); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - } - continue; - } - - String rowId = next.getElement(); - // 获取下一次调度前的延迟时间。 - double delay = conn.zscore("delay:", rowId); - if (delay <= 0) { - // 不必再缓存这个行,将它从缓存中移除。 - conn.zrem("delay:", rowId); - conn.zrem("schedule:", rowId); - conn.del("inv:" + rowId); - continue; - } - - // 读取数据行。 - Inventory row = Inventory.get(rowId); - // 更新调度时间并设置缓存值。 - conn.zadd("schedule:", now + delay, rowId); - conn.set("inv:" + rowId, gson.toJson(row)); - } - } - -} -``` - -### 案例-网页分析 - -网站可以采集用户的访问、交互、购买行为,再分析用户习惯、喜好,从而判断市场行情和潜在商机等。 - -那么,简单的,如何记录用户在一定时间内访问的商品页面呢? - -参考 [更新令牌](#更新令牌) 代码示例,记录用户访问不同商品的浏览次数,并排序。 - -判断页面是否需要缓存,根据评分判断商品页面是否热门: - -```java - public boolean canCache(Jedis conn, String request) { - try { - URL url = new URL(request); - HashMap params = new HashMap<>(); - if (url.getQuery() != null) { - for (String param : url.getQuery().split("&")) { - String[] pair = param.split("=", 2); - params.put(pair[0], pair.length == 2 ? pair[1] : null); - } - } - - // 尝试从页面里面取出商品ID。 - String itemId = extractItemId(params); - // 检查这个页面能否被缓存以及这个页面是否为商品页面。 - if (itemId == null || isDynamic(params)) { - return false; - } - // 取得商品的浏览次数排名。 - Long rank = conn.zrank("viewed:", itemId); - // 根据商品的浏览次数排名来判断是否需要缓存这个页面。 - return rank != null && rank < 10000; - } catch (MalformedURLException mue) { - return false; - } - } -``` - -### 案例-记录日志 - -可用使用 `LIST` 结构存储日志数据。 - -```java - public void logRecent(Jedis conn, String name, String message, String severity) { - String destination = "recent:" + name + ':' + severity; - Pipeline pipe = conn.pipelined(); - pipe.lpush(destination, TIMESTAMP.format(new Date()) + ' ' + message); - pipe.ltrim(destination, 0, 99); - pipe.sync(); - } -``` - -### 案例-统计数据 - -更新计数器: - -```java - public static final int[] PRECISION = new int[] { 1, 5, 60, 300, 3600, 18000, 86400 }; - - public void updateCounter(Jedis conn, String name, int count, long now) { - Transaction trans = conn.multi(); - for (int prec : PRECISION) { - long pnow = (now / prec) * prec; - String hash = String.valueOf(prec) + ':' + name; - trans.zadd("known:", 0, hash); - trans.hincrBy("count:" + hash, String.valueOf(pnow), count); - } - trans.exec(); - } -``` - -查看计数器数据: - -```java - public List> getCounter( - Jedis conn, String name, int precision) { - String hash = String.valueOf(precision) + ':' + name; - Map data = conn.hgetAll("count:" + hash); - List> results = new ArrayList<>(); - for (Map.Entry entry : data.entrySet()) { - results.add(new Pair<>( - entry.getKey(), - Integer.parseInt(entry.getValue()))); - } - Collections.sort(results); - return results; - } -``` - -### 案例-查找 IP 所属地 - -Redis 实现的 IP 所属地查找比关系型数据实现方式更快。 - -#### 载入 IP 数据 - -IP 地址转为整数值: - -```java - public int ipToScore(String ipAddress) { - int score = 0; - for (String v : ipAddress.split("\\.")) { - score = score * 256 + Integer.parseInt(v, 10); - } - return score; - } -``` - -创建 IP 地址与城市 ID 之间的映射: - -```java - public void importIpsToRedis(Jedis conn, File file) { - FileReader reader = null; - try { - // 载入 csv 文件数据 - reader = new FileReader(file); - CSVFormat csvFormat = CSVFormat.DEFAULT.withRecordSeparator("\n"); - CSVParser csvParser = csvFormat.parse(reader); - int count = 0; - List records = csvParser.getRecords(); - for (CSVRecord line : records) { - String startIp = line.get(0); - if (startIp.toLowerCase().indexOf('i') != -1) { - continue; - } - // 将 IP 地址转为整数值 - int score = 0; - if (startIp.indexOf('.') != -1) { - score = ipToScore(startIp); - } else { - try { - score = Integer.parseInt(startIp, 10); - } catch (NumberFormatException nfe) { - // 略过文件的第一行以及格式不正确的条目 - continue; - } - } - - // 构建唯一的城市 ID - String cityId = line.get(2) + '_' + count; - // 将城市 ID 及其对应的 IP 地址整数值添加到 ZSET - conn.zadd("ip2cityid:", score, cityId); - count++; - } - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - try { - reader.close(); - } catch (Exception e) { - // ignore - } - } - } -``` - -存储城市信息: - -```java - public void importCitiesToRedis(Jedis conn, File file) { - Gson gson = new Gson(); - FileReader reader = null; - try { - // 加载 csv 信息 - reader = new FileReader(file); - CSVFormat csvFormat = CSVFormat.DEFAULT.withRecordSeparator("\n"); - CSVParser parser = new CSVParser(reader, csvFormat); - // String[] line; - List records = parser.getRecords(); - for (CSVRecord record : records) { - - if (record.size() < 4 || !Character.isDigit(record.get(0).charAt(0))) { - continue; - } - - // 将城市地理信息转为 json 结构,存入 HASH 结构中 - String cityId = record.get(0); - String country = record.get(1); - String region = record.get(2); - String city = record.get(3); - String json = gson.toJson(new String[] { city, region, country }); - conn.hset("cityid2city:", cityId, json); - } - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - try { - reader.close(); - } catch (Exception e) { - // ignore - } - } - } -``` - -#### 查找 IP 所属城市 - -操作步骤: - -1. 将要查找的 IP 地址转为整数值; -2. 查找所有分值小于等于要查找的 IP 地址的地址,取出其中最大分值的那个记录; -3. 用找到的记录所对应的城市 ID 去检索城市信息。 - -```java - public String[] findCityByIp(Jedis conn, String ipAddress) { - int score = ipToScore(ipAddress); - Set results = conn.zrevrangeByScore("ip2cityid:", score, 0, 0, 1); - if (results.size() == 0) { - return null; - } - - String cityId = results.iterator().next(); - cityId = cityId.substring(0, cityId.indexOf('_')); - return new Gson().fromJson(conn.hget("cityid2city:", cityId), String[].class); - } -``` - -### 案例-服务的发现与配置 - -### 案例-自动补全 - -需求:根据用户输入,自动补全信息,如:联系人、商品名等。 - -- 典型场景一:社交网站后台记录用户最近联系过的 100 个好友,当用户查找好友时,根据输入的关键字自动补全姓名。 -- 典型场景二:电商网站后台记录用户最近浏览过的 10 件商品,当用户查找商品是,根据输入的关键字自动补全商品名称。 - -数据模型:使用 Redis 的 LIST 类型存储最近联系人列表。 - -构建自动补全列表通常有以下操作: - -- 如果指定联系人已经存在于最近联系人列表里,那么从列表里移除他。对应 `LREM` 命令。 -- 将指定联系人添加到最近联系人列表的最前面。对应 `LPUSH` 命令。 -- 添加操作完成后,如果联系人列表中的数量超过 100 个,进行裁剪操作。对应 `LTRIM` 命令。 - -### 案例-广告定向 - -### 案例-职位搜索 - -需求:在一个招聘网站上,求职者有自己的技能清单;用人公司的职位有必要的技能清单。用人公司需要查询满足自己职位要求的求职者;求职者需要查询自己可以投递简历的职位。 - -关键数据模型:使用 `SET` 类型存储求职者的技能列表,使用 `SET` 类型存储职位的技能列表。 - -关键操作:使用 `SDIFF` 命令对比两个 `SET` 的差异,返回 `empty` 表示匹配要求。 - -redis cli 示例: - -```shell -# ----------------------------------------------------------- -# Redis 职位搜索数据模型示例 -# ----------------------------------------------------------- - -# (1)职位技能表:使用 set 存储 -# job:001 职位添加 4 种技能 -SADD job:001 skill:001 -SADD job:001 skill:002 -SADD job:001 skill:003 -SADD job:001 skill:004 - -# job:002 职位添加 3 种技能 -SADD job:002 skill:001 -SADD job:002 skill:002 -SADD job:002 skill:003 - -# job:003 职位添加 2 种技能 -SADD job:003 skill:001 -SADD job:003 skill:003 - -# 查看 -SMEMBERS job:001 -SMEMBERS job:002 -SMEMBERS job:003 - -# (2)求职者技能表:使用 set 存储 -SADD interviewee:001 skill:001 -SADD interviewee:001 skill:003 - -SADD interviewee:002 skill:001 -SADD interviewee:002 skill:002 -SADD interviewee:002 skill:003 -SADD interviewee:002 skill:004 -SADD interviewee:002 skill:005 - -# 查看 -SMEMBERS interviewee:001 -SMEMBERS interviewee:002 - -# (3)求职者遍历查找自己符合要求的职位(返回结果为 empty 表示要求的技能全部命中) -# 比较职位技能清单和求职者技能清单的差异 -SDIFF job:001 interviewee:001 -SDIFF job:002 interviewee:001 -SDIFF job:003 interviewee:001 - -SDIFF job:001 interviewee:002 -SDIFF job:002 interviewee:002 -SDIFF job:003 interviewee:002 - -# (4)用人公司根据遍历查找符合自己职位要求的求职者(返回结果为 empty 表示要求的技能全部命中) -# 比较职位技能清单和求职者技能清单的差异 -SDIFF interviewee:001 job:001 -SDIFF interviewee:002 job:001 - -SDIFF interviewee:001 job:002 -SDIFF interviewee:002 job:002 - -SDIFF interviewee:001 job:003 -SDIFF interviewee:002 job:003 -``` - -## 参考资料 - -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **教程** - - [Redis 命令参考](http://redisdoc.com/) -- **文章** - - [一看就懂系列之 详解 redis 的 bitmap 在亿级项目中的应用](https://blog.csdn.net/u011957758/article/details/74783347) - - [Fast, easy, realtime metrics using Redis bitmaps](http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 0000000000..8a5c0a6152 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/03.Redis\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,717 @@ +--- +icon: logos:redis +title: Redis 数据结构 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/20230901071535.png +date: 2023-08-23 15:14:13 +order: 03 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 数据结构 + - 链表 + - 字典 + - 跳表 +permalink: /pages/aae60d/ +--- + +# Redis 数据结构 + +> 关键词:`对象`、`SDS`、`链表`、`字典`、`跳表`、`整数集合`、`压缩列表` + +## SDS + +### SDS 简介 + +SDS 是 Simple Dynamic String 的缩写,即**简单动态字符串**。Redis 为 SDS 做了一些优化,以替代 C 字符串来表示字符串内容。此外,SDS 还被 Redis 用作缓冲区(buffer),如:AOF 模块中的 AOF 缓冲区;客户端状态中的输入缓冲区。 + +SDS 相比 C 字符串,具有以下优势: + +| C 字符串 | SDS | +| :--------------------------------------------------- | :--------------------------------------------------- | +| 获取字符串长度的复杂度为 O(N) 。 | 获取字符串长度的复杂度为 O(1) 。 | +| API 是不安全的,可能会造成缓冲区溢出。 | API 是安全的,不会造成缓冲区溢出。 | +| 修改字符串长度 `N` 次必然需要执行 `N` 次内存重分配。 | 修改字符串长度 `N` 次最多需要执行 `N` 次内存重分配。 | +| 只能保存文本数据。 | 可以保存文本或者二进制数据。 | +| 可以使用所有 `` 库中的函数。 | 可以使用一部分 `` 库中的函数。 | + +### SDS 实现 + +每个 `sds.h/sdshdr` 结构表示一个 SDS 值: + +```c +struct sdshdr { + + // 记录 buf 数组中已使用字节的数量 + // 等于 SDS 所保存字符串的长度 + int len; + + // 记录 buf 数组中未使用字节的数量 + int free; + + // 字节数组,用于保存字符串 + char buf[]; + +}; +``` + +SDS 遵循 C 字符串以空字符结尾的惯例, 保存空字符的 `1` 字节空间不计算在 SDS 的 `len` 属性里面, 并且为空字符分配额外的 `1` 字节空间, 以及添加空字符到字符串末尾等操作都是由 SDS 函数自动完成的, 所以这个空字符对于 SDS 的使用者来说是完全透明的。 + +![](http://redisbook.com/_images/graphviz-72760f6945c3742eca0df91a91cc379168eda82d.png) + +### SDS 特性 + +SDS 与 C 字符串相比,做了一些优化,具有以下优势: + +#### 常数复杂度获取字符串长度 + +- C 字符串 - 因为 C 字符串并不记录自身的长度信息, 所以为了获取一个 C 字符串的长度, 程序必须遍历整个字符串, 对遇到的每个字符进行计数, 直到遇到代表字符串结尾的空字符为止, 这个操作的复杂度为 `O(N)` 。 +- SDS - 因为 SDS 在 `len` 属性中记录了 SDS 本身的长度, 所以获取一个 SDS 长度的复杂度仅为 `O(1)` 。设置和更新 SDS 长度的工作是由 SDS 的 API 在执行时自动完成的, 使用 SDS 无须进行任何手动修改长度的工作。 + +#### 杜绝缓冲区溢出 + +- C 字符串 - C 字符串不记录自身长度带来的另一个问题是容易造成缓冲区溢出(buffer overflow)。 +- SDS - 当 SDS API 需要对 SDS 进行修改时, API 会先检查 SDS 的空间是否满足修改所需的要求, 如果不满足的话, API 会自动将 SDS 的空间扩展至执行修改所需的大小, 然后才执行实际的修改操作, 所以使用 SDS 既不需要手动修改 SDS 的空间大小, 也不会出现前面所说的缓冲区溢出问题。 + +#### 减少修改字符串长度时所需的内存重分配次数 + +- C 字符串 - 对于一个包含了 `N` 个字符的 C 字符串来说, 这个 C 字符串的底层实现总是一个 `N+1` 个字符长的数组(额外的一个字符空间用于保存空字符)。因为 C 字符串的长度和底层数组的长度之间存在着这种关联性, 所以每次增长或者缩短一个 C 字符串, 程序都总要对保存这个 C 字符串的数组进行一次内存重分配操作。 + - 增长字符串时,如果没有内存重分配,就会产生缓冲区溢出。 + - 缩减字符串是,如果没有内存重分配,就会产生内存泄露。 +- SDS - 因为内存重分配涉及复杂的算法, 并且可能需要执行系统调用, 所以它通常是一个比较耗时的操作。SDS 通过未使用空间解除了字符串长度和底层数组长度之间的关联: 在 SDS 中, `buf` 数组的长度不一定就是字符数量加一, 数组里面可以包含未使用的字节, 而这些字节的数量就由 SDS 的 `free` 属性记录。通过未使用空间, SDS 实现了空间预分配和惰性空间释放两种优化策略。 + - **空间预分配** - **空间预分配用于优化 SDS 的字符串增长操作**。 当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为 SDS 分配额外的未使用空间。通过空间预分配, SDS 将连续增长 `N` 次字符串所需的内存重分配次数从必定 `N` 次降低为最多 `N` 次。 + - **惰性空间** - **惰性空间释放用于优化 SDS 的字符串缩短操作**。当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 `free` 属性将这些字节的数量记录起来, 并等待将来使用。通过惰性空间释放策略, SDS 避免了缩短字符串时所需的内存重分配操作, 并为将来可能有的增长操作提供了优化。 + +#### 二进制安全 + +- C 字符串 - C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 **C 字符串只能保存文本数据**, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。 +- SDS - SDS 的 API 都是二进制安全的(binary-safe): 所有 SDS API 都会以处理二进制的方式来处理 SDS 存放在 `buf` 数组里的数据, 程序不会对其中的数据做任何限制、过滤、或者假设 —— 数据在写入时是什么样的, 它被读取时就是什么样。**通过使用二进制安全的 SDS , 使得 Redis 不仅可以保存文本数据, 还可以保存任意格式的二进制数据**。 + +#### 兼容部分 C 字符串函数 + +虽然 SDS 的 API 都是二进制安全的, 但也会遵循 C 字符串惯例,将保存的数据的末尾设置为空字符, 并且总会在为 `buf` 数组分配空间时多分配一个字节来容纳这个空字符, 这是为了让那些保存文本数据的 SDS 可以重用一部分 `` 库定义的函数。因此,SDS 可以兼容部分 C 字符串函数。 + +## 链表 + +### 链表简介 + +**链表被广泛用于实现 Redis 的各种功能,比如 List 键,订阅与发布,慢查询,监视器等**。此外,Redis 服务器本身还使用链表来保存多个客户端的状态信息, 以及使用链表来构建客户端输出缓冲区(output buffer)。 + +由于 C 语言没有内置的链表,因此 Redis 自实现了一个链表:**Redis 的链表实现其实就是一个双链表**。 + +- 每个链表使用一个 list 结构来表示,这个结构带有表头节点指针、表尾节点指针、以及链表长度等信息。 +- 因为链表表头节点的前置节点和表尾节点的后置节点都指向 NULL,所以 Redis 的链表实现是无环链表。 +- 通过为链表设置不同的类型特定函数,Redis 的链表可以用于保存各种不同类型的值。 + +### 链表实现 + +每个链表节点由一个 `adlist.h/listNode` 结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以 Redis 的链表实现是双链表。 + +```c +typedef struct listNode { + + // 前置节点 + struct listNode *prev; + + // 后置节点 + struct listNode *next; + + // 节点的值 + void *value; + +} listNode; +``` + +多个 `listNode` 可以通过 `prev` 和 `next` 指针组成双链表。 + +![](http://redisbook.com/_images/graphviz-167adfc2e52e078d4c0e3c8a9eddec54551602fb.png) + +虽然仅仅使用多个 `listNode` 结构就可以组成链表, 但使用 `adlist.h/list` 来持有链表的话, 操作起来会更方便: + +```c +typedef struct list { + + // 表头节点 + listNode *head; + + // 表尾节点 + listNode *tail; + + // 链表所包含的节点数量 + unsigned long len; + + // 节点值复制函数 + void *(*dup)(void *ptr); + + // 节点值释放函数 + void (*free)(void *ptr); + + // 节点值对比函数 + int (*match)(void *ptr, void *key); + +} list; +``` + +`list` 结构为链表提供了表头指针 `head` 、表尾指针 `tail` , 以及链表长度计数器 `len` , 而 `dup` 、 `free` 和 `match` 成员则是用于实现多态链表所需的类型特定函数: + +- `dup` 函数 - 用于复制链表节点所保存的值; +- `free` 函数 - 用于释放链表节点所保存的值; +- `match` 函数 - 用于对比链表节点所保存的值和另一个输入值是否相等。 + +![](http://redisbook.com/_images/graphviz-5f4d8b6177061ac52d0ae05ef357fceb52e9cb90.png) + +## 字典 + +### 字典简介 + +字典是一种用于保存键值对(key-value pair)的抽象数据结构。字典中的每个键都是独一无二的, 程序可以在字典中根据键查找与之关联的值, 或者通过键来更新值, 又或者根据键来删除整个键值对, 等等。 + +由于 C 语言没有内置的链表,因此 Redis 自实现了一个字典。 + +字典被广泛用于实现 Redis 的各种功能, 其中包括数据库和 Hash 键。 + +### 字典实现 + +Redis 的字典使用哈希表作为底层实现, 一个哈希表里面可以有多个哈希表节点, 而每个哈希表节点就保存了字典中的一个键值对。 + +Redis 字典所使用的哈希表由 `dict.h/dictht` 结构定义: + +```c +typedef struct dictht { + + // 哈希表数组 + dictEntry **table; + + // 哈希表大小 + unsigned long size; + + // 哈希表大小掩码,用于计算索引值 + // 总是等于 size - 1 + unsigned long sizemask; + + // 该哈希表已有节点的数量 + unsigned long used; + +} dictht; +``` + +- `table` 属性是一个数组, 数组中的每个元素都是一个指向 `dict.h/dictEntry` 结构的指针, 每个 `dictEntry` 结构保存着一个键值对。 +- `size` 属性记录了哈希表的大小, 也即是 `table` 数组的大小, 而 `used` 属性则记录了哈希表目前已有节点(键值对)的数量。 +- `sizemask` 属性的值总是等于 `size - 1` , 这个属性和哈希值一起决定一个键应该被放到 `table` 数组的哪个索引上面。 + +哈希表节点使用 `dictEntry` 结构表示, 每个 `dictEntry` 结构都保存着一个键值对: + +```c +typedef struct dictEntry { + + // 键 + void *key; + + // 值 + union { + void *val; + uint64_t u64; + int64_t s64; + } v; + + // 指向下个哈希表节点,形成链表 + struct dictEntry *next; + +} dictEntry; +``` + +- `key` 属性保存着键值对中的键, 而 `v` 属性则保存着键值对中的值, 其中键值对的值可以是一个指针, 或者是一个 `uint64_t` 整数, 又或者是一个 `int64_t` 整数。 +- `next` 属性是指向另一个哈希表节点的指针, 这个指针可以将多个哈希值相同的键值对连接在一次, 以此来解决键冲突(collision)的问题。 + +Redis 中的字典由 `dict.h/dict` 结构表示: + +```c +typedef struct dict { + + // 类型特定函数 + dictType *type; + + // 私有数据 + void *privdata; + + // 哈希表 + dictht ht[2]; + + // rehash 索引 + // 当 rehash 不在进行时,值为 -1 + int rehashidx; /* rehashing not in progress if rehashidx == -1 */ + +} dict; +``` + +`type` 属性和 `privdata` 属性是针对不同类型的键值对, 为创建多态字典而设置的: + +- `type` 属性是一个指向 `dictType` 结构的指针, 每个 `dictType` 结构保存了一簇用于操作特定类型键值对的函数, Redis 会为用途不同的字典设置不同的类型特定函数。 +- 而 `privdata` 属性则保存了需要传给那些类型特定函数的可选参数。 +- `ht` 属性是一个包含两个项的数组, 数组中的每个项都是一个 `dictht` 哈希表, 一般情况下, 字典只使用 `ht[0]` 哈希表, `ht[1]` 哈希表只会在对 `ht[0]` 哈希表进行 rehash 时使用。 +- `rehashidx` 属性记录了 rehash 目前的进度, 如果目前没有在进行 rehash , 那么它的值为 `-1` 。 + +![](http://redisbook.com/_images/graphviz-e73003b166b90094c8c4b7abbc8d59f691f91e27.png) + +### 哈希算法 + +当字典被用作数据库的底层实现, 或者哈希键的底层实现时, **Redis 使用 [MurmurHash2](http://code.google.com/p/smhasher/) 算法来计算键的哈希值**。 + +Redis 计算哈希值和索引值的方法如下: + +```c +# 使用字典设置的哈希函数,计算键 key 的哈希值 +hash = dict->type->hashFunction(key); + +# 使用哈希表的 sizemask 属性和哈希值,计算出索引值 +# 根据情况不同, ht[x] 可以是 ht[0] 或者 ht[1] +index = hash & dict->ht[x].sizemask; +``` + +### 哈希冲突 + +当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时, 我们称这些键发生了冲突(collision)。 + +**Redis 使用链地址法(separate chaining)来解决哈希冲突**: 每个哈希表节点都有一个 `next` 指针, 多个哈希表节点可以用 `next` 指针构成一个单向链表, 被分配到同一个索引上的多个节点可以用这个单向链表连接起来, 这就解决了键冲突的问题。 + +![](http://redisbook.com/_images/graphviz-4b52dcf6eb0768750e1c15480be3326ca37e05b3.png) + +### rehash + +#### rehash 的步骤 + +1. 为字典的 `ht[1]` 哈希表分配空间, 这个哈希表的空间大小取决于要执行的操作, 以及 `ht[0]` 当前包含的键值对数量 (也即是 ht[0].used 属性的值)。 +2. 将保存在 `ht[0]` 中的所有键值对 rehash 到 `ht[1]` 上面: rehash 指的是重新计算键的哈希值和索引值, 然后将键值对放置到 `ht[1]` 哈希表的指定位置上。 +3. 当 `ht[0]` 包含的所有键值对都迁移到了 `ht[1]` 之后 (`ht[0]` 变为空表), 释放 `ht[0]` , 将 `ht[1]` 设置为 `ht[0]` , 并在 `ht[1]` 新创建一个空白哈希表, 为下一次 rehash 做准备。 + +![](http://redisbook.com/_images/graphviz-93608325578e8e45848938ef420115bf2227639e.png) + +![](http://redisbook.com/_images/graphviz-b68acb4d868ec7d79a44935ce08a159746ca58da.png) + +![](http://redisbook.com/_images/graphviz-92dc47e4329eabae941cddfd727b736ef738e8cf.png) + +![](http://redisbook.com/_images/graphviz-fa28d986a72f1f48b83c7f959ea217b1f9527d3c.png) + +#### rehash 的条件 + +当以下条件中的任意一个被满足时, 程序会自动开始对哈希表执行扩展操作: + +1. 服务器目前没有在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 `1` ; +2. 服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 `5` ; + +其中哈希表的负载因子可以通过公式计算得出: + +``` +# 负载因子 = 哈希表已保存节点数量 / 哈希表大小 +load_factor = ht[0].used / ht[0].size +``` + +#### 渐进式 rehash + +渐进式 rehash 的详细步骤: + +1. 为 `ht[1]` 分配空间, 让字典同时持有 `ht[0]` 和 `ht[1]` 两个哈希表。 +2. 在字典中维持一个索引计数器变量 `rehashidx` , 并将它的值设置为 `0` , 表示 rehash 工作正式开始。 +3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 `ht[0]` 哈希表在 `rehashidx` 索引上的所有键值对 rehash 到 `ht[1]` , 当 rehash 工作完成之后, 程序将 `rehashidx` 属性的值增一。 +4. 随着字典操作的不断执行, 最终在某个时间点上, `ht[0]` 的所有键值对都会被 rehash 至 `ht[1]` , 这时程序将 `rehashidx` 属性的值设为 `-1` , 表示 rehash 操作已完成。 + +## 跳表 + +### 跳表简介 + +跳表(skiplist)是一种有序数据结构, 它通过在每个节点中维持多个指向其他节点的指针, 从而达到快速访问节点的目的。 + +跳表支持平均 O(log N) 最坏 O(N) 复杂度的节点查找, 还可以通过顺序性操作来批量处理节点。 + +在大部分情况下, 跳表的效率可以和平衡树相媲美, 并且因为跳表的实现比平衡树要来得更为简单, 所以有不少程序都使用跳表来代替平衡树。 + +Redis 使用跳表作为有序集合键的底层实现之一: 如果一个有序集合包含的元素数量比较多, 又或者有序集合中元素的成员(member)是比较长的字符串时, Redis 就会使用跳表来作为有序集合键的底层实现。 + +此外,Redis 还在集群节点中用跳表作为内部数据结构。 + +### 跳表实现 + +Redis 的跳表实现由 `zskiplist` 和 `zskiplistNode` 两个结构组成, 其中 `zskiplist` 用于保存跳表信息(比如表头节点、表尾节点、长度), 而 `zskiplistNode` 则用于表示跳表节点。 + +![](http://redisbook.com/_images/graphviz-59432127803598137980d030e8e529c5b068bebb.png) + +`zskiplist` 结构的定义如下: + +```c +typedef struct zskiplist { + + // 表头节点和表尾节点 + struct zskiplistNode *header, *tail; + + // 表中节点的数量 + unsigned long length; + + // 表中层数最大的节点的层数 + int level; + +} zskiplist; +``` + +- `header` 和 `tail` 指针分别指向跳表的表头和表尾节点, 通过这两个指针, 程序定位表头节点和表尾节点的复杂度为 O(1) 。 +- 通过使用 `length` 属性来记录节点的数量, 程序可以在 O(1) 复杂度内返回跳表的长度。 +- `level` 属性则用于在 O(1) 复杂度内获取跳表中层高最大的那个节点的层数量, 注意表头节点的层高并不计算在内。**每个跳表节点的层高都是 `1` 至 `32` 之间的随机数**。 + +跳表节点的实现由 `redis.h/zskiplistNode` 结构定义: + +```c +typedef struct zskiplistNode { + + // 后退指针 + struct zskiplistNode *backward; + + // 分值 + double score; + + // 成员对象 + robj *obj; + + // 层 + struct zskiplistLevel { + + // 前进指针 + struct zskiplistNode *forward; + + // 跨度 + unsigned int span; + + } level[]; + +} zskiplistNode; +``` + +- 层(level):每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。在上面的图片中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。 +- 后退(backward)指针:它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。 +- 分值(score):在跳表中,节点按各自所保存的分值从小到大排列。**在同一个跳表中, 多个节点可以包含相同的分值, 但每个节点的成员对象必须是唯一的**。跳表中的节点按照分值大小进行排序, 当分值相同时, 节点按照成员对象的大小进行排序。 +- 成员对象(obj):各个节点中的 `o1` 、 `o2` 和 `o3` 是节点所保存的成员对象。 + +## 整数集合 + +### 整数集合简介 + +**整数集合(intset)是集合键的底层实现之一**。当一个集合只包含整数值元素, 并且这个集合的元素数量不多时, Redis 就会使用整数集合作为集合键的底层实现。 + +**整数集合的底层实现为数组, 这个数组以有序、无重复的方式保存集合元素, 在有需要时, 程序会根据新添加元素的类型, 改变这个数组的类型**。 + +**升级操作为整数集合带来了操作上的灵活性, 并且尽可能地节约了内存**。 + +**整数集合只支持升级操作, 不支持降级操作**。 + +### 整数集合实现 + +整数集合是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 `int16_t` 、 `int32_t` 或者 `int64_t` 的整数值, 并且保证集合中不会出现重复元素。 + +每个 `intset.h/intset` 结构表示一个整数集合: + +```c +typedef struct intset { + + // 编码方式 + uint32_t encoding; + + // 集合包含的元素数量 + uint32_t length; + + // 保存元素的数组 + int8_t contents[]; + +} intset; +``` + +- `contents` 数组是整数集合的底层实现: 整数集合的每个元素都是 `contents` 数组的一个数组项(item), 各个项在数组中按值的大小从小到大有序地排列, 并且数组中不包含任何重复项。 +- `length` 属性记录了整数集合包含的元素数量, 也即是 `contents` 数组的长度。 +- 虽然 `intset` 结构将 `contents` 属性声明为 `int8_t` 类型的数组, 但实际上 `contents` 数组并不保存任何 `int8_t` 类型的值 —— `contents` 数组的真正类型取决于 `encoding` 属性的值: + - 如果 `encoding` 属性的值为 `INTSET_ENC_INT16` , 那么 `contents` 就是一个 `int16_t` 类型的数组, 数组里的每个项都是一个 `int16_t` 类型的整数值 (最小值为 `-32,768` ,最大值为 `32,767` )。 + - 如果 `encoding` 属性的值为 `INTSET_ENC_INT32` , 那么 `contents` 就是一个 `int32_t` 类型的数组, 数组里的每个项都是一个 `int32_t` 类型的整数值 (最小值为 `-2,147,483,648` ,最大值为 `2,147,483,647` )。 + - 如果 `encoding` 属性的值为 `INTSET_ENC_INT64` , 那么 `contents` 就是一个 `int64_t` 类型的数组, 数组里的每个项都是一个 `int64_t` 类型的整数值 (最小值为 `-9,223,372,036,854,775,808` ,最大值为 `9,223,372,036,854,775,807` )。 + +![](http://redisbook.com/_images/graphviz-acf7fe010d7b09c5d2500c72eb555863e67ad74f.png) + +![](http://redisbook.com/_images/graphviz-878c08b90e7bbd02863d3e5cad116b36785ea30e.png) + +### 整数集合升级 + +每当我们要将一个新元素添加到整数集合里面, 并且新元素的类型比整数集合现有所有元素的类型都要长时, 整数集合需要先进行升级(upgrade), 然后才能将新元素添加到整数集合里面。 + +升级整数集合并添加新元素共分为三步进行: + +1. 根据新元素的类型, 扩展整数集合底层数组的空间大小, 并为新元素分配空间。 +2. 将底层数组现有的所有元素都转换成与新元素相同的类型, 并将类型转换后的元素放置到正确的位上, 而且在放置元素的过程中, 需要继续维持底层数组的有序性质不变。 +3. 将新元素添加到底层数组里面。 + +因为每次向整数集合添加新元素都可能会引起升级, 而每次升级都需要对底层数组中已有的所有元素进行类型转换, 所以向整数集合添加新元素的时间复杂度为 O(N) 。 + +## 压缩列表 + +### 压缩列表简介 + +**压缩列表是一种为节约内存而开发的顺序型数据结构**。 + +**压缩列表(ziplist)被用作列表键和哈希键的底层实现之一**。 + +- 当一个列表键只包含少量列表项, 并且每个列表项要么就是小整数值, 要么就是长度比较短的字符串, 那么 Redis 就会使用压缩列表来做列表键的底层实现。 +- 当一个哈希键只包含少量键值对, 并且每个键值对的键和值要么就是小整数值, 要么就是长度比较短的字符串, 那么 Redis 就会使用压缩列表来做哈希键的底层实现。 + +**压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值**。 + +添加新节点到压缩列表, 或者从压缩列表中删除节点, 可能会引发连锁更新操作, 但这种操作出现的几率并不高。 + +### 压缩列表实现 + +压缩列表各个组成部分的详细说明 + +| 属性 | 类型 | 长度 | 用途 | +| :-------- | :--------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `zlbytes` | `uint32_t` | `4` 字节 | 记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配, 或者计算 `zlend` 的位置时使用。 | +| `zltail` | `uint32_t` | `4` 字节 | 记录压缩列表表尾节点距离压缩列表的起始地址有多少字节: 通过这个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址。 | +| `zllen` | `uint16_t` | `2` 字节 | 记录了压缩列表包含的节点数量: 当这个属性的值小于 `UINT16_MAX` (`65535`)时, 这个属性的值就是压缩列表包含节点的数量; 当这个值等于 `UINT16_MAX` 时, 节点的真实数量需要遍历整个压缩列表才能计算得出。 | +| `entryX` | 列表节点 | 不定 | 压缩列表包含的各个节点,节点的长度由节点保存的内容决定。 | +| `zlend` | `uint8_t` | `1` 字节 | 特殊值 `0xFF` (十进制 `255` ),用于标记压缩列表的末端。 | + +## 对象 + +Redis 并没有直接使用这些数据结构来实现键值对数据库, 而是基于这些数据结构创建了一个对象系统, 这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象, 每种对象都用到了至少一种我们前面所介绍的数据结构。 + +### 对象简介 + +Redis 数据库中的每个键值对的键和值都是一个对象。 + +Redis 共有字符串、列表、哈希、集合、有序集合五种类型的对象, 每种类型的对象至少都有两种或以上的编码方式, 不同的编码可以在不同的使用场景上优化对象的使用效率。 + +服务器在执行某些命令之前, 会先检查给定键的类型能否执行指定的命令, 而检查一个键的类型就是检查键的值对象的类型。 + +**基于引用计数技术的内存回收机制** - Redis 的对象系统带有引用计数实现的内存回收机制, 当一个对象不再被使用时, 该对象所占用的内存就会被自动释放。 + +**基于引用计数技术的对象共享机制** - Redis 会共享值为 `0` 到 `9999` 的字符串对象。 + +**计算数据库键的空转时长** - 对象会记录自己的最后一次被访问的时间, 这个时间可以用于计算对象的空转时间。 + +### 对象的类型 + +**Redis 使用对象来表示数据库中的键和值**。每次当我们在 Redis 的数据库中新创建一个键值对时, 我们至少会创建两个对象, 一个对象用作键值对的键(键对象), 另一个对象用作键值对的值(值对象)。 + +Redis 中的每个对象都由一个 `redisObject` 结构表示, 该结构中和保存数据有关的三个属性分别是 `type` 属性、 `encoding` 属性和 `ptr` 属性: + +```c +typedef struct redisObject { + + // 类型 + unsigned type:4; + + // 编码 + unsigned encoding:4; + + // 指向底层实现数据结构的指针 + void *ptr; + + // ... + +} robj; +``` + +对象的 `type` 属性记录了对象的类型,有以下类型: + +| 对象 | 对象 `type` 属性的值 | TYPE 命令的输出 | +| :----------- | :------------------- | :-------------- | +| 字符串对象 | `REDIS_STRING` | `"string"` | +| 列表对象 | `REDIS_LIST` | `"list"` | +| 哈希对象 | `REDIS_HASH` | `"hash"` | +| 集合对象 | `REDIS_SET` | `"set"` | +| 有序集合对象 | `REDIS_ZSET` | `"zset"` | + +Redis 数据库保存的键值对来说, 键总是一个字符串对象, 而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种。 + +【示例】通过 `TYPE` 命令查看数据库键的值对象的类型 + +```shell +# 键为字符串对象,值为字符串对象 +> SET msg "hello world" +OK +> TYPE msg +string + +# 键为字符串对象,值为列表对象 +> RPUSH numbers 1 3 5 +(integer) 6 +> TYPE numbers +list + +# 键为字符串对象,值为哈希对象 +> HMSET profile name Tome age 25 career Programmer +OK +> TYPE profile +hash + +# 键为字符串对象,值为集合对象 +> SADD fruits apple banana cherry +(integer) 3 +> TYPE fruits +set + +# 键为字符串对象,值为有序集合对象 +> ZADD price 8.5 apple 5.0 banana 6.0 cherry +(integer) 3 +> TYPE price +zset +``` + +### 对象的编码 + +对象的 `ptr` 指针指向对象的底层实现数据结构, 而这些数据结构由对象的 `encoding` 属性决定。 + +`encoding` 属性记录了对象所使用的编码, 也即是说这个对象使用了什么数据结构作为对象的底层实现。 + +Redis 中每种类型的对象都至少使用了两种不同的编码,**不同的编码可以在不同的使用场景上优化对象的使用效率**。 + +Redis 支持的编码如下所示: + +| 类型 | 编码 | 对象 | **OBJECT ENCODING** **命令输出** | +| :------------- | :-------------------------- | :--------------------------------------------------- | -------------------------------- | +| `REDIS_STRING` | `REDIS_ENCODING_INT` | 使用整数值实现的字符串对象。 | "int" | +| `REDIS_STRING` | `REDIS_ENCODING_EMBSTR` | 使用 `embstr` 编码的简单动态字符串实现的字符串对象。 | "embstr" | +| `REDIS_STRING` | `REDIS_ENCODING_RAW` | 使用简单动态字符串实现的字符串对象。 | "raw" | +| `REDIS_LIST` | `REDIS_ENCODING_ZIPLIST` | 使用压缩列表实现的列表对象。 | "ziplist" | +| `REDIS_LIST` | `REDIS_ENCODING_LINKEDLIST` | 使用双端链表实现的列表对象。 | "linkedlist" | +| `REDIS_HASH` | `REDIS_ENCODING_ZIPLIST` | 使用压缩列表实现的哈希对象。 | "ziplist" | +| `REDIS_HASH` | `REDIS_ENCODING_HT` | 使用字典实现的哈希对象。 | "hashtable" | +| `REDIS_SET` | `REDIS_ENCODING_INTSET` | 使用整数集合实现的集合对象。 | "intset" | +| `REDIS_SET` | `REDIS_ENCODING_HT` | 使用字典实现的集合对象。 | "hashtable" | +| `REDIS_ZSET` | `REDIS_ENCODING_ZIPLIST` | 使用压缩列表实现的有序集合对象。 | "ziplist" | +| `REDIS_ZSET` | `REDIS_ENCODING_SKIPLIST` | 使用跳表和字典实现的有序集合对象。 | "skiplist" | + +【示例】使用 `OBJECT ENCODING` 命令可以查看数据库键的值对象的编码 + +```shell +> SET msg "hello wrold" +OK +> OBJECT ENCODING msg +"embstr" + +> SET story "long long long long long long ago ..." +OK +> OBJECT ENCODING story +"raw" + +> SADD numbers 1 3 5 +(integer) 3 +> OBJECT ENCODING numbers +"intset" + +> SADD numbers "seven" +(integer) 1 +> OBJECT ENCODING numbers +"hashtable" +``` + +### 类型检查与命令多态 + +Redis 中用于操作键的命令基本上可以分为两种类型。 + +- **多态命令** - 可以对任何类型的键执行。如 DEL、 EXPIRE 、 RENAME 、 TYPE 、 OBJECT 等命令。 +- **特定类型命令** + - SET 、 GET 、 APPEND 、 STRLEN 等命令只能对字符串键执行; + - HDEL 、 HSET 、 HGET 、 HLEN 等命令只能对哈希键执行; + - RPUSH 、 LPOP 、 LINSERT 、 LLEN 等命令只能对列表键执行; + - SADD 、 SPOP 、 SINTER 、 SCARD 等命令只能对集合键执行; + - ZADD 、 ZCARD 、 ZRANK 、 ZSCORE 等命令只能对有序集合键执行; + +为了确保只有指定类型的键可以执行某些特定的命令,Redis 在执行一个类型特定的命令之前, Redis 会先检查输入键的类型是否正确, 然后再决定是否执行给定的命令。类型特定命令所进行的类型检查是通过 `redisObject` 结构的 `type` 属性来实现的: + +- 在执行一个类型特定命令之前, 服务器会先检查输入数据库键的值对象是否为执行命令所需的类型, 如果是的话, 服务器就对键执行指定的命令; +- 否则, 服务器将拒绝执行命令, 并向客户端返回一个类型错误。 + +Redis 除了会根据值对象的类型来判断键是否能够执行指定命令之外, 还会根据值对象的编码方式, 选择正确的命令实现代码来执行命令。 + +### 内存回收 + +由于 C 语言不支持内存回收,Redis 内部实现了一套基于引用计数的内存回收机制。 + +每个对象的引用计数信息由 `redisObject` 结构的 `refcount` 属性记录: + +```c +typedef struct redisObject { + + // ... + + // 引用计数 + int refcount; + + // ... + +} robj; +``` + +对象的引用计数信息会随着对象的使用状态而不断变化: + +- 在创建一个新对象时, 引用计数的值会被初始化为 `1` ; +- 当对象被一个新程序使用时, 它的引用计数值会被增一; +- 当对象不再被一个程序使用时, 它的引用计数值会被减一; +- 当对象的引用计数值变为 `0` 时, 对象所占用的内存会被释放。 + +### 对象共享 + +在 Redis 中, 让多个键共享同一个值对象需要执行以下两个步骤: + +1. 将数据库键的值指针指向一个现有的值对象; +2. 将被共享的值对象的引用计数增一。 + +共享对象机制对于节约内存非常有帮助, 数据库中保存的相同值对象越多, 对象共享机制就能节约越多的内存。 + +Redis 会在初始化服务器时, 共享值为 `0` 到 `9999` 的字符串对象。 + +### 对象的空转时长 + +`redisObject` 的 `lru` 属性记录了对象最后一次被命令程序访问的时间: + +```c +typedef struct redisObject { + + // ... + + unsigned lru:22; + + // ... + +} robj; +``` + +OBJECT IDLETIME 命令可以打印出给定键的空转时长, 这一空转时长就是通过将当前时间减去键的值对象的 `lru` 时间计算得出的: + +``` +> SET msg "hello world" +OK + +# 等待一小段时间 +> OBJECT IDLETIME msg +(integer) 20 + +# 等待一阵子 +> OBJECT IDLETIME msg +(integer) 180 + +# 访问 msg 键的值 +> GET msg +"hello world" + +# 键处于活跃状态,空转时长为 0 +> OBJECT IDLETIME msg +(integer) 0 +``` + +> 注意 +> +> OBJECT IDLETIME 命令的实现是特殊的, 这个命令在访问键的值对象时, 不会修改值对象的 `lru` 属性。 + +除了可以被 OBJECT IDLETIME 命令打印出来之外, 键的空转时长还有另外一项作用: 如果服务器打开了 `maxmemory` 选项, 并且服务器用于回收内存的算法为 `volatile-lru` 或者 `allkeys-lru` , 那么当服务器占用的内存数超过了 `maxmemory` 选项所设置的上限值时, 空转时长较高的那部分键会优先被服务器释放, 从而回收内存。 + +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" deleted file mode 100644 index 03bce695fc..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/06.Redis\345\223\250\345\205\265.md" +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: Redis 哨兵 -date: 2020-06-24 10:45:38 -categories: - - 数据库 - - KV数据库 - - Redis -tags: - - 数据库 - - KV数据库 - - Redis - - 哨兵 -permalink: /pages/615afe/ ---- - -# Redis 哨兵 - -> Redis 哨兵(Sentinel)是 Redis 的**高可用性**(Hight Availability)解决方案。 -> -> Redis 哨兵是 [Raft 算法](https://dunwu.github.io/blog/pages/4907dc/) 的具体实现。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713072747.png) - -## 一、哨兵简介 - -Redis 哨兵(Sentinel)是 Redis 的**高可用性**(Hight Availability)解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131135847.png) - -Sentinel 的主要功能如下: - -- **`监控(Monitoring)`** - Sentinel 不断检查主从服务器是否正常在工作。 -- **`通知(Notification)`** - Sentinel 可以通过一个 api 来通知系统管理员或者另外的应用程序,被监控的 Redis 实例有一些问题。 -- **`自动故障转移(Automatic Failover)`** - 如果一个主服务器下线,Sentinel 会开始自动故障转移:把一个从节点提升为主节点,并重新配置其他的从节点使用新的主节点,使用 Redis 服务的应用程序在连接的时候也被通知新的地址。 -- **`配置提供者(Configuration provider)`** - Sentinel 给客户端的服务发现提供来源:对于一个给定的服务,客户端连接到 Sentinels 来寻找当前主节点的地址。当故障转移发生的时候,Sentinel 将报告新的地址。 - -## 二、启动哨兵 - -启动一个 Sentinel 可以使用下面任意一条命令,两条命令效果完全相同。 - -```shell -redis-sentinel /path/to/sentinel.conf -redis-server /path/to/sentinel.conf --sentinel -``` - -当一个 Sentinel 启动时,它需要执行以下步骤: - -1. 初始化服务器。 -2. 使用 Sentinel 专用代码。 -3. 初始化 Sentinel 状态。 -4. 初始化 Sentinel 的主服务器列表。 -5. 创建连向被监视的主服务器的网络连接。 - -**Sentinel 本质上是一个运行在特殊状模式下的 Redis 服务器**。 - -Sentinel 模式下 Redis 服务器只支持 `PING`、`SENTINEL`、`INFO`、`SUBSCRIBE`、`UNSUBSCRIBE`、`PSUBSCRIBE`、`PUNSUBSCRIBE` 七个命令。 - -创建连向被监视的主服务器的网络连接,Sentinel 将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息。对于每个被 Sentinel 监视的主服务器,Sentinel 会创建两个连向主服务器的异步网络: - -- 命令连接:专门用于向主服务器发送命令,并接受命令回复。 -- 订阅连接:专门用于订阅主服务器的 `__sentinel__:hello` 频道。 - -## 三、监控 - -### 检测服务器状态 - -> **Sentinel 向 Redis 服务器发送 `PING` 命令,检查其状态**。 - -默认情况下,**每个** `Sentinel` 节点会以 **每秒一次** 的频率对 `Redis` 节点和 **其它** 的 `Sentinel` 节点发送 `PING` 命令,并通过节点的 **回复** 来判断节点是否在线。 - -- **主观下线**:**主观下线** 适用于所有 **主节点** 和 **从节点**。如果在 `down-after-milliseconds` 毫秒内,`Sentinel` 没有收到 **目标节点** 的有效回复,则会判定 **该节点** 为 **主观下线**。 -- **客观下线**:**客观下线** 只适用于 **主节点**。当 `Sentinel` 将一个主服务器判断为主管下线后,为了确认这个主服务器是否真的下线,会向同样监视这一主服务器的其他 Sentinel 询问,看它们是否也认为主服务器已经下线。当足够数量的 Sentinel 认为主服务器已下线,就判定其为客观下线,并对其执行故障转移操作。 - - `Sentinel` 节点通过 `sentinel is-master-down-by-addr` 命令,向其它 `Sentinel` 节点询问对该节点的 **状态判断**。 - -### 获取服务器信息 - -> **Sentinel 向主服务器发送 `INFO` 命令,获取主服务器及它的从服务器信息**。 - -- **获取主服务器信息** - Sentinel **默认**会以**每十秒一次**的频率,通过命令连接**向被监视的主服务器发送 `INFO` 命令,并通过分析 `INFO` 命令的回复来获取主服务器的当前信息**。 - - 主服务自身信息:包括 run_id 域记录的服务器运行 ID,以及 role 域记录的服务器角色 - - 主服务的从服务器信息:包括 IP 地址和端口号 -- **获取从服务器信息** - 当 Sentinel 发现主服务器有新的从服务器出现时,Sentinel 除了会为这个新的从服务器创建相应的实例结构之外,Sentinel 还会创建连接到从服务器的命令连接和订阅连接。 - -## 四、通知 - -对于每个与 Sentinel 连接的服务器,Sentinel 既会向服务器的 `__sentinel__:hello` 频道发送消息,也会订阅服务器的 `__sentinel__:hello` 频道的消息。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131153842.png) - -### 向服务器发送消息 - -在默认情况下,Sentinel 会以每两秒一次的频率,通过命令向所有被监视的主服务器和从服务器发送以下格式的命令。 - -``` -PUBLISH __sentinel__:hello ",,,,,,," -``` - -这条命令向服务器的 `__sentinel__:hello` 频道发送一条消息。 - -### 接收服务器的消息 - -当 Sentinel 与一个主服务器或从服务器建立起订阅连接后,Sentinel 就会通过订阅连接,向服务器发送以下命令:`SUBSCRIBE __sentinel__:hello`。 - -Sentinel 对 `__sentinel__:hello` 频道的订阅会一直持续到 Sentinel 与服务器断开连接为止。 - -## 五、选举 Leader - -> Redis Sentinel 系统选举 Leader 的算法是 [Raft](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) 的实现。 -> -> Raft 是一种共识性算法,想了解其原理,可以参考 [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/4907dc/)。 - -**当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头的 Sentinel,并由领头 Sentinel 对下线主服务器执行故障转移操作**。 - -所有在线 Sentinel 都有资格被选为 Leader。 - -每个 `Sentinel` 节点都需要 **定期执行** 以下任务: - -(1)每个 `Sentinel` 以 **每秒钟** 一次的频率,向它所知的 **主服务器**、**从服务器** 以及其他 `Sentinel` **实例** 发送一个 `PING` 命令。 - -![img](https://user-gold-cdn.xitu.io/2018/8/22/16560ce61df44c4d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -(2)如果一个 **实例**(`instance`)距离 **最后一次** 有效回复 `PING` 命令的时间超过 `down-after-milliseconds` 所指定的值,那么这个实例会被 `Sentinel` 标记为 **主观下线**。 - -![img](https://user-gold-cdn.xitu.io/2018/8/22/16560ce61dc739de?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -(3)如果一个 **主服务器** 被标记为 **主观下线**,那么正在 **监视** 这个 **主服务器** 的所有 `Sentinel` 节点,要以 **每秒一次** 的频率确认 **主服务器** 的确进入了 **主观下线** 状态。 - -![img](https://user-gold-cdn.xitu.io/2018/8/22/16560ce647a39535?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -(4)如果一个 **主服务器** 被标记为 **主观下线**,并且有 **足够数量** 的 `Sentinel`(至少要达到 **配置文件** 指定的数量)在指定的 **时间范围** 内同意这一判断,那么这个 **主服务器** 被标记为 **客观下线**。 - -![img](https://user-gold-cdn.xitu.io/2018/8/22/16560ce647c2583e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -(5)在一般情况下, 每个 `Sentinel` 会以每 `10` 秒一次的频率,向它已知的所有 **主服务器** 和 **从服务器** 发送 `INFO` 命令。当一个 **主服务器** 被 `Sentinel` 标记为 **客观下线** 时,`Sentinel` 向 **下线主服务器** 的所有 **从服务器** 发送 `INFO` 命令的频率,会从 `10` 秒一次改为 **每秒一次**。 - -![img](https://user-gold-cdn.xitu.io/2018/8/22/16560ce6738a30db?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -(6)`Sentinel` 和其他 `Sentinel` 协商 **主节点** 的状态,如果 **主节点** 处于 `SDOWN` 状态,则投票自动选出新的 **主节点**。将剩余的 **从节点** 指向 **新的主节点** 进行 **数据复制**。 - -![img](https://user-gold-cdn.xitu.io/2018/8/22/16560ce676a95a54?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -(7)当没有足够数量的 `Sentinel` 同意 **主服务器** 下线时, **主服务器** 的 **客观下线状态** 就会被移除。当 **主服务器** 重新向 `Sentinel` 的 `PING` 命令返回 **有效回复** 时,**主服务器** 的 **主观下线状态** 就会被移除。 - -![img](https://user-gold-cdn.xitu.io/2018/8/22/16560ce6759c1cb3?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -> 注意:一个有效的 `PING` 回复可以是:`+PONG`、`-LOADING` 或者 `-MASTERDOWN`。如果 **服务器** 返回除以上三种回复之外的其他回复,又或者在 **指定时间** 内没有回复 `PING` 命令, 那么 `Sentinel` 认为服务器返回的回复 **无效**(`non-valid`)。 - -## 六、故障转移 - -在选举产生出 Sentinel Leader 后,Sentinel Leader 将对已下线的主服务器执行故障转移操作。操作含以下三个步骤: - -(一)**选出新的主服务器** - -故障转移第一步,是 Sentinel Leader 在已下线主服务属下的所有从服务器中,挑选一个状态良好、数据完整的从服务器。然后,向这个从服务器发送 `SLAVEOF no one` 命令,将其转换为主服务器。 - -Sentinel Leader 如何选出新的主服务器: - -- 删除列表中所有处于下线或断线状态的从服务器。 -- 删除列表中所有最近五秒没有回复过 Sentinel Leader 的 INFO 命令的从服务器。 -- 删除所有与已下线主服务器连接断开超过 `down-after-milliseconds` \* 10 毫秒的从服务器(`down-after-milliseconds` 指定了判断主服务器下线所需的时间)。 -- 之后, Sentinel Leader 先选出优先级最高的从服务器;如果优先级一样高,再选择复制偏移量最大的从服务器;如果结果还不唯一,则选出运行 ID 最小的从服务器。 - -(二)**修改从服务器的复制目标** - -选出新的主服务器后,Sentinel Leader 会向所有从服务器发送 `SLAVEOF` 命令,让它们去复制新的主服务器。 - -(三)**将旧的主服务器变为从服务器** - -Sentinel Leader 将旧的主服务器标记为从服务器。当旧的主服务器重新上线,Sentinel 会向它发送 SLAVEOF 命令,让其成为从服务器。 - -## 参考资料 - -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **教程** - - [Redis 命令参考](http://redisdoc.com/) -- **文章** - - [渐进式解析 Redis 源码 - 哨兵 sentinel](http://www.web-lovers.com/redis-source-sentinel.html) - - [深入剖析 Redis 系列(二) - Redis 哨兵模式与高可用集群](https://juejin.im/post/5b7d226a6fb9a01a1e01ff64) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" deleted file mode 100644 index bc425f9aac..0000000000 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/08.Redis\345\256\236\346\210\230.md" +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Redis 实战 -date: 2020-06-24 10:45:38 -categories: - - 数据库 - - KV数据库 - - Redis -tags: - - 数据库 - - KV数据库 - - Redis -permalink: /pages/1fc9c4/ ---- - -# Redis 实战 - -## 一、应用场景 - -Redis 可以应用于很多场景,这里列举几个经典的应用场景。 - -### 缓存 - -缓存是 Redis 最常见的应用场景。 - -Redis 有多种数据类型,以及丰富的操作命令,并且有着高性能、高可用的特性,非常适合用于分布式缓存。 - -> 缓存应用的基本原理,请参考 [**缓存基本原理**](https://dunwu.github.io/design/distributed/分布式缓存.html) 第四 ~ 第六节内容。 - -### BitMap 和 BloomFilter - -Redis 除了 5 种基本数据类型外,还支持 BitMap 和 BloomFilter(即布隆过滤器,可以通过 Redis Module 支持)。 - -BitMap 和 BloomFilter 都可以用于解决缓存穿透问题。要点在于:过滤一些不可能存在的数据。 - -> 什么是缓存穿透,可以参考:[**缓存基本原理**](https://dunwu.github.io/design/distributed/分布式缓存.html) - -小数据量可以用 BitMap,大数据量可以用布隆过滤器。 - -### 分布式锁 - -使用 Redis 作为分布式锁,基本要点如下: - -- **互斥性** - 使用 `setnx` 抢占锁。 -- **避免永远不释放锁** - 使用 `expire` 加一个过期时间,避免一直不释放锁,导致阻塞。 -- **原子性** - setnx 和 expire 必须合并为一个原子指令,避免 setnx 后,机器崩溃,没来得及设置 expire,从而导致锁永不释放。 - -> 更多分布式锁的实现方式及细节,请参考:[分布式锁基本原理](https://dunwu.github.io/blog/pages/40ac64/) - -## 二、技巧 - -根据 Redis 的特性,在实际应用中,存在一些应用小技巧。 - -### keys 和 scan - -使用 `keys` 指令可以扫出指定模式的 key 列表。 - -如果这个 redis 正在给线上的业务提供服务,那使用 `keys` 指令会有什么问题? - -首先,Redis 是单线程的。`keys` 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。 - -这个时候可以使用 `scan` 指令,`scan` 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 `keys` 指令长。 - -不过,增量式迭代命令也不是没有缺点的: 举个例子, 使用 `SMEMBERS` 命令可以返回集合键当前包含的所有元素, 但是对于 `SCAN` 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。 - -## 参考资料 - -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **教程** - - [Redis 命令参考](http://redisdoc.com/) -- **文章** - - [《我们一起进大厂》系列- Redis 基础](https://juejin.im/post/5db66ed9e51d452a2f15d833) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/11.Redis\350\277\207\346\234\237\345\210\240\351\231\244\345\222\214\345\206\205\345\255\230\346\267\230\346\261\260.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/11.Redis\350\277\207\346\234\237\345\210\240\351\231\244\345\222\214\345\206\205\345\255\230\346\267\230\346\261\260.md" new file mode 100644 index 0000000000..08cfa0a8a8 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/11.Redis\350\277\207\346\234\237\345\210\240\351\231\244\345\222\214\345\206\205\345\255\230\346\267\230\346\261\260.md" @@ -0,0 +1,180 @@ +--- +icon: logos:redis +title: Redis 过期删除和内存淘汰 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202309171630222.png +date: 2023-08-23 15:14:13 +order: 11 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - LRU + - LFU +permalink: /pages/ce0453/ +--- + +# Redis 过期删除和内存淘汰 + +> 关键词:`定时删除`、`惰性删除`、`定期删除`、`LRU`、`LFU` + +## Redis 过期删除 + +Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。 + +### 设置键的生存时间或过期时间 + +Redis 中,和键的生存时间相关的命令如下所示: + +| 命令 | 描述 | +| --------------------------------------------------- | --------------------------------------- | +| [`EXPIRE`](https://redis.io/commands/expire/) | 设置 key 的过期时间,单位为秒 | +| [`PEXPIRE`](https://redis.io/commands/pexpire/) | 设置 key 的过期时间,单位为毫秒 | +| [`EXPIREAT`](https://redis.io/commands/expireat/) | 设置 key 的过期时间为指定的秒级时间戳 | +| [`PEXPIREAT`](https://redis.io/commands/pexpireat/) | 设置 key 的过期时间为指定的毫秒级时间戳 | +| [`TTL`](https://redis.io/commands/ttl/) | 返回 key 的剩余生存时间,单位为秒 | +| [`PTTL`](https://redis.io/commands/pttl/) | 返回 key 的剩余生存时间,单位为毫秒 | +| [`PERSIST`](https://redis.io/commands/persist/) | 移除 key 的过期时间,key 将持久保持 | + +【示例】EXPIRE、TTL 操作 + +```shell +> set key value +OK +# 设置 key 的生存时间为 60s +> expire key 60 +(integer) 1 +# 查看 key 的剩余生存时间 +> ttl key +(integer) 58 +# 60s 之内 +> get key +"value" +# 60s 之外 +> get key +(nil) +``` + +【示例】EXPIREAT、TTL 操作 + +```shell +> set key value +OK +# 设置 key 的生存时间为 1692419299 +> expireat key 1692419299 +(integer) 1 +# 查看 key 的剩余生存时间 +> ttl key +(integer) 9948 +# 1692419299 之前 +> get key +"value" +# 1692419299 之后 +> get key +(nil) +``` + +#### 如何保存过期时间 + +在 Redis 中,redisDb 结构的 `expires` 字典保存了数据库中所有键的过期时间,这个字典称为过期字典: + +- 过期字典的键是一个指针,这个指针指向某个键对象 +- 过期字典的值是一个 `long long` 类型的整数,这个整数保存了键的过期时间——一个毫秒精度的 UNIX 时间戳。 + +```c +typedef struct redisDb { + + // 数据库键空间,保存着数据库中的所有键值对 + dict *dict; + + // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳 + dict *expires; + + // ... +} redisDb; +``` + +下图是一个带有过期字典的示例: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309171537744.png) + +当执行 `EXPIRE`、`PEXPIRE`、`EXPIREAT`、`PEXPIREAT` 命令,Redis 都会将其转为 `PEXPIREAT` 形式的时间戳,然后维护在 `expires` 字典中。 + +#### 过期键的判定 + +过期键的判定流程如下: + +- 检查指定 key 是否存在于过期字典;如果存在,则取得 key 的过期时间。 +- 检查当前时间戳是否大于 key 的过期时间:如果是,key 已过期;反之,key 未过期。 + +### 过期删除策略 + +- **定时删除** - 在设置 key 的过期时间的同时,创建一个定时器,让定时器在 key 的过期时间来临时,立即执行对 key 的删除操作。 + - 优点 - 保证过期 key 被尽可能快的删除,释放内存。 + - 缺点 - **如果过期 key 较多,可能会占用相当一部分的 CPU,从而影响服务器的吞吐量和响应时延**。 +- **惰性删除** - 放任 key 过期不管,但是每次访问 key 时,都检查 key 是否过期,如果过期的话,就删除该 key ;如果没有过期,就返回该 key。 + - 优点 - 占用 CPU 最少。程序只会在读写键时,对当前键进行过期检查,因此不会有额外的 CPU 开销。 + - 缺点 - **过期的 key 可能因为没有被访问,而一直无法释放,造成内存的浪费,有内存泄漏的风险**。 +- **定期删除** - 每隔一段时间,程序就对数据库进行一次检查,删除里面的过期 key 。至于要删除多少过期 key ,以及要检查多少个数据库,则由算法决定。定期删除是前两种策略的一种折中方案。定期删除策略的难点是删除操作执行的时长和频率。 + - 执行太频或执行时间过长,就会出现和定时删除相同的问题; + - 执行太少或执行时间过短,就会出现和惰性删除相同的问题; + +### Redis 的过期删除策略 + +Redis 同时采用了惰性删除和定期删除策略,以此在合理使用 CPU 和内存之间取得平衡。 + +**Redis 定期删除策略的实现** - 由 `redis.c/activeExpireCycle` 函数实现,每当 Redis 周期性执行 `redis.c/serverCron` 函数时,`activeExpireCycle` 函数就会被调用。`activeExpireCycle` 函数会在规定时间内,遍历各个数据库,从 `expires` 字典中随机检查一部分键的过期时间,并删除过期的键。 + +**Redis 惰性删除策略的实现** - 由 `db.c/expireIfNeeded` 函数实现,所有读写命令在执行之前都会调用 `expireIfNeeded` 函数对输入键进行检查:如果输入键已过期,将输入键从数据库中删除;否则,什么也不做。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309171604805.png) + +### AOF、RDB 和复制对过期键的处理 + +- 生成 RDB 文件 - **执行 `SAVE` 命令或者 `BGSAVE` 命令,所产生的新 RDB 文件“不会包含已经过期的键”**。 +- 载入 RDB 文件 - **主服务器“不会载入已过期的键”**;**从服务器会载入“会载入已过期的键”**。 +- 生成 AOF 文件 - 当一个过期键未被删除时,不会影响 AOF 文件;当一个过期键被删除之后, 服务器会追加一条 `DEL` 命令到现有 AOF 文件的末尾, 显式地删除过期键。 +- 重写 AOF 文件 - **执行 `BGREWRITEAOF` 命令所产生的重写 AOF 文件“不会包含已经过期的键”**。 +- 复制 - 当主服务器删除一个过期键之后, 它会向所有从服务器发送一条 `DEL` 命令, 显式地删除过期键。从服务器即使发现过期键, 也不会自作主张地删除它, 而是等待主节点发来 DEL 命令, 这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。 +- 当 Redis 命令对数据库进行修改之后, 服务器会根据配置, 向客户端发送数据库通知。 + +## Redis 内存淘汰 + +### Redis 内存淘汰要点 + +- **失效时间** - 作为一种定期清理无效数据的重要机制,在 Redis 提供的诸多命令中,`EXPIRE`、`EXPIREAT`、`PEXPIRE`、`PEXPIREAT` 以及 `SETEX` 和 `PSETEX` 均可以用来设置一条键值对的失效时间。而一条键值对一旦被关联了失效时间就会在到期后自动删除(或者说变得无法访问更为准确)。 +- **最大缓存** - Redis 允许通过 `maxmemory` 参数来设置内存最大值。当内存达设定的阀值,就会触发**内存淘汰**。 +- **内存淘汰** - 内存淘汰是为了更好的利用内存——清理部分缓存,以此换取内存的利用率,即尽量保证 Redis 缓存中存储的是热点数据。 +- **非精准的 LRU** - 实际上 Redis 实现的 LRU 并不是可靠的 LRU,也就是名义上我们使用 LRU 算法淘汰键,但是实际上被淘汰的键并不一定是真正的最久没用的。 + +### Redis 内存淘汰策略 + +内存淘汰只是 Redis 提供的一个功能,为了更好地实现这个功能,必须为不同的应用场景提供不同的策略,内存淘汰策略讲的是为实现内存淘汰我们具体怎么做,要解决的问题包括淘汰键空间如何选择?在键空间中淘汰键如何选择? + +Redis 提供了下面几种内存淘汰策略供用户选: + +- **不淘汰** + - **`noeviction`** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。这是 Redis 默认的策略。 +- **在过期键中进行淘汰** + - **`volatile-random`** - 在设置了过期时间的键空间中,随机移除某个 key。 + - **`volatile-ttl`** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。 + - **`volatile-lru`** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。 + - **`volatile-lfu`** (Redis 4.0 新增)- 淘汰所有设置了过期时间的键值中,最少使用的键值。 +- **在所有键中进行淘汰** + - **`allkeys-lru`** - 在主键空间中,优先移除最近未使用的 key。 + - **`allkeys-random`** - 在主键空间中,随机移除某个 key。 + - **`allkeys-lfu`** (Redis 4.0 新增) - 淘汰整个键值中最少使用的键值。 + +### 如何选择淘汰策略 + +- 如果数据呈现幂等分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 `allkeys-lru` 或 `allkeys-lfu`。 +- 如果数据呈现平均分布,也就是所有的数据访问频率都相同,则使用 `allkeys-random`。 +- 若 Redis 既用于缓存,也用于持久化存储时,适用 `volatile-lru` 、`volatile-lfu`、`volatile-random`。但是,这种情况下,也可以部署两个 Redis 集群来达到同样目的。 +- 为 key 设置过期时间实际上会消耗更多的内存。因此,如果条件允许,建议使用 `allkeys-lru` 或 `allkeys-lfu`,从而更高效的使用内存。 + +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/12.Redis\346\214\201\344\271\205\345\214\226.md" similarity index 57% rename from "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" rename to "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/12.Redis\346\214\201\344\271\205\345\214\226.md" index 8c5b4e9683..84881437f8 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/04.Redis\346\214\201\344\271\205\345\214\226.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/12.Redis\346\214\201\344\271\205\345\214\226.md" @@ -1,6 +1,9 @@ --- +icon: logos:redis title: Redis 持久化 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202309150716562.png date: 2020-06-24 10:45:38 +order: 12 categories: - 数据库 - KV数据库 @@ -10,89 +13,146 @@ tags: - KV数据库 - Redis - 持久化 + - CoW permalink: /pages/4de901/ --- # Redis 持久化 -> Redis 支持持久化,即把数据存储到硬盘中。 +> Redis 是内存型数据库,为了保证数据在宕机后不会丢失,需要将内存中的数据持久化到硬盘上。 > -> Redis 提供了两种持久化方式: +> Redis 支持两种持久化方式:RDB 和 AOF。这两种持久化方式既可以同时使用,也可以单独使用。 > -> - **`RDB 快照(snapshot)`** - 将存在于某一时刻的所有数据都写入到硬盘中。 -> - **`只追加文件(append-only file,AOF)`** - 它会在执行写命令时,将被执行的写命令复制到硬盘中。 -> -> 这两种持久化方式既可以同时使用,也可以单独使用。 -> -> 将内存中的数据存储到硬盘的一个主要原因是为了在之后重用数据,或者是为了防止系统故障而将数据备份到一个远程位置。另外,存储在 Redis 里面的数据有可能是经过长时间计算得出的,或者有程序正在使用 Redis 存储的数据进行计算,所以用户会希望自己可以将这些数据存储起来以便之后使用,这样就不必重新计算了。 -> -> Redis 提供了两种持久方式:RDB 和 AOF。你可以同时开启两种持久化方式。在这种情况下, 当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。 +> 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` -## 一、RDB +## RDB 快照 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309150718907.png) ### RDB 简介 -**RDB 即快照方式,它将某个时间点的所有 Redis 数据保存到一个经过压缩的二进制文件(RDB 文件)中**。 +**RDB 即“快照”,它将某时刻的所有 Redis 数据库中的所有键值对数据保存到一个经过压缩的“二进制文件”(RDB 文件)中**。 + +**RDB 持久化即可以“手动”执行,也可以定期“自动”执行**。 + +**RDB 文件的“载入”工作是在服务器“启动”时“自动”执行的**。 -创建 RDB 后,用户可以对 RDB 进行**备份**,可以将 RDB **复制**到其他服务器从而创建具有相同数据的服务器副本,还可以在**重启**服务器时使用。一句话来说:RDB 适合作为 **冷备**。 +对于不同类型的键值对, RDB 文件会使用不同的方式来保存它们。 -RDB 既可以手动执行,也可以根据服务器配置选项定期执行。该功能可以将某个时间点的数据库状态保存到一个 RDB 文件中。 +创建 RDB 后,用户可以对 RDB 进行备份,可以将 RDB 复制到其他服务器从而创建具有相同数据的服务器副本,还可以在重启服务器时使用。一句话来说:**RDB 适用于作为“冷备”**。 -#### RDB 的优点 +### RDB 的优点和缺点 -- RDB 文件非常紧凑,**适合作为冷备**。比如你可以在每个小时报保存一下过去 24 小时内的数据,同时每天保存过去 30 天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。 +**RDB 的优点** + +- RDB 文件非常紧凑,**适合作为“冷备”**。比如你可以在每个小时报保存一下过去 24 小时内的数据,同时每天保存过去 30 天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。 - 快照在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以快照持久化方式可以最大化 Redis 的性能。 - **恢复大数据集时,RDB 比 AOF 更快**。 -#### RDB 的缺点 +**RDB 的缺点** - **如果系统发生故障,将会丢失最后一次创建快照之后的数据**。如果你希望在 Redis 意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么 快照不适合你。虽然你可以配置不同的 save 时间点(例如每隔 5 分钟并且对数据集有 100 个写的操作),是 Redis 要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔 5 分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。 - **如果数据量很大,保存快照的时间会很长**。快照需要经常 fork 子进程来保存数据集到硬盘上。当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且 CPU 性能不是很好的情况下,这种情况会持续 1 秒。AOF 也需要 fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。 ### RDB 的创建 -有两个 Redis 命令可以用于生成 RDB 文件:`SAVE` 和 `BGSAVE`。 - -- [**`SAVE`**](https://redis.io/commands/save) 命令会阻塞 Redis 服务器进程,直到 RDB 创建完成为止,在阻塞期间,服务器不能响应任何命令请求。 -- [**`BGSAVE`**](https://redis.io/commands/bgsave) 命令会派生出(fork)一个子进程,然后由子进程负责创建 RDB 文件,服务器进程(父进程)继续处理命令请求。 - -> :bell: 注意:`BGSAVE` 命令执行期间,`SAVE`、`BGSAVE`、`BGREWRITEAOF` 三个命令会被拒绝,以免与当前的 `BGSAVE` 操作产生竞态条件,降低性能。 +有两个 Redis 命令可以用于生成 RDB 文件:[**`SAVE`**](https://redis.io/commands/save) 和 [**`BGSAVE`**](https://redis.io/commands/bgsave) 。 -#### 自动间隔保存 +[**`SAVE`**](https://redis.io/commands/save) 命令由服务器进程直接执行保存操作,直到 RDB 创建完成为止。所以**该命令“会阻塞”服务器**,在阻塞期间,服务器不能响应任何命令请求。 -Redis 允许用户通过设置服务器配置的 `save` 选项,让服务器每隔一段时间自动执行一次 `BGSAVE` 命令。 +```shell +>SAVE +"OK" +``` -用户可以通过 `save` 选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行 `BGSAVE` 命令。 +[**`BGSAVE`**](https://redis.io/commands/bgsave) 命令会**“派生”**(fork)一个子进程,由子进程负责创建 RDB 文件,服务器进程继续处理命令请求,所以**该命令“不会阻塞”服务器**。 -举例来说,`redis.conf` 中设置了如下配置: +![BGSAVE 流程](https://raw.githubusercontent.com/dunwu/images/master/snap/202309172009198.png) -``` -save 900 1 -- 900 秒内,至少对数据库进行了 1 次修改 -save 300 10 -- 300 秒内,至少对数据库进行了 10 次修改 -save 60 10000 -- 60 秒内,至少对数据库进行了 10000 次修改 +```shell +>BGSAVE +"Background saving started" ``` -只要满足以上任意条件,Redis 服务就会执行 BGSAVE 命令。 +> 🔔 **【注意】** +> +> `BGSAVE` 命令的实现采用的是写时复制技术(Copy-On-Write,缩写为 CoW)。 +> +> `BGSAVE` 命令执行期间,`SAVE`、`BGSAVE`、`BGREWRITEAOF` 三个命令会被拒绝,以免与当前的 `BGSAVE` 操作产生竞态条件,降低性能。 + +创建 RDB 的工作由 `rdb.c/rdbSave` 函数完成。 ### RDB 的载入 -**RDB 文件的载入工作是在服务器启动时自动执行的**,Redis 并没有专门用于载入 RDB 文件的命令。 +**RDB 文件的“载入”工作是在服务器“启动”时“自动”执行的**。Redis 并没有专门用于载入 RDB 文件的命令。 服务器载入 RDB 文件期间,会一直处于阻塞状态,直到载入完成为止。 -> 🔔 注意:因为 AOF 通常更新频率比 RDB 高,所以丢失数据相对更少。基于这个原因,Redis 有以下默认行为: +载入 RDB 的工作由 `rdb.c/rdbLoad` 函数完成。 + +> 🔔 **【注意】** > -> - 只有在关闭 AOF 功能的情况下,才会使用 RDB 还原数据,否则优先使用 AOF 文件来还原数据。 +> 因为 AOF 的更新频率通常比 RDB 的更新频率高,所以: +> +> - 如果服务器开了 AOF,则服务器会优先使用 AOF 来还原数据。 +> - 只有在 AOF 处于关闭时,服务器才会使用 RDB 来还原数据。 + +### 自动间隔保存 + +Redis 支持通过在 `redis.conf` 文件中配置 `save` 选项,让服务器每隔一段时间自动执行一次 `BGSAVE` 命令。`save` 选项可以设置多个保存条件,只要其中任意一个条件被满足,服务器就会执行 `BGSAVE` 命令。 + +【示例】`redis.conf` 中自动保存配置 + +```shell +# 900 秒内,至少对数据库进行了 1 次修改 +save 900 1 +# 300 秒内,至少对数据库进行了 10 次修改 +save 300 10 +# 60 秒内,至少对数据库进行了 10000 次修改 +save 60 10000 +``` + +只要满足以上任意条件,Redis 服务就会执行 `BGSAVE` 命令。 + +自动间隔的保存条件定义在 `redis.h/redisServer` 中: + +```c +struct redisServer { + // 记录了保存条件的数组 + struct saveparam *saveparams; + + // 自从上次 SAVE 执行以来,数据库被修改的次数 + long long dirty; + + // 上一次完成 SAVE 的时间 + time_t lastsave; +} + +// 服务器的保存条件(BGSAVE 自动执行的条件) +struct saveparam { + + // 多少秒之内 + time_t seconds; + + // 发生多少次修改 + int changes; + +}; +``` + +redisServer 中的 `saveparams` 数组维护了多个自动间隔保存条件。 + +服务每次成功执行一个修改命令后,`dirty` 计数器就会加 1;而 `lastsave` 则记录了上一次完成 SAVE 的时间。Redis 会通过一个 `serverCron` 函数周期性检查 `save` 选项所设条件是否满足,如果满足,则执行 `BGSVAE` 命令。 ### RDB 的文件结构 -RDB 文件是一个经过压缩的二进制文件,由多个部分组成。 +**RDB 文件是一个经过压缩的“二进制文件”**,由多个部分组成。 对于不同类型(STRING、HASH、LIST、SET、SORTED SET)的键值对,RDB 文件会使用不同的方式来保存它们。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-rdb-structure.png) +![RDB 的文件结构](https://raw.githubusercontent.com/dunwu/images/master/snap/202309171645336.png) -Redis 本身提供了一个 RDB 文件检查工具 redis-check-dump。 +Redis 本身提供了一个 RDB 文件检查工具 `redis-check-dump`。 ### RDB 的配置 @@ -111,32 +171,34 @@ dir ./ Redis 的配置文件 `redis.conf` 中与 RDB 有关的选项: -- `save` - Redis 会根据 `save` 选项,让服务器每隔一段时间自动执行一次 `BGSAVE` 命令。 +- `save` - Redis 会根据 `save` 选项,让服务器每隔一段时间自动执行一次 `BGSAVE` 命令 -- `stop-writes-on-bgsave-error` - 当 BGSAVE 命令出现错误时停止写 RDB 文件 -- `rdbcompression` - RDB 文件开启压缩功能。 -- `rdbchecksum` - 对 RDB 文件进行校验。 -- `dbfilename` - RDB 文件名。 -- `dir` - RDB 文件和 AOF 文件的存储路径。 +- `stop-writes-on-bgsave-error` - 当 `BGSAVE` 命令出现错误时停止写 RDB 文件 +- `rdbcompression` - RDB 文件开启压缩功能 +- `rdbchecksum` - 对 RDB 文件进行校验 +- `dbfilename` - RDB 文件名 +- `dir` - RDB 文件和 AOF 文件的存储路径 + +## AOF 日志 -## 二、AOF +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309150718055.png) ### AOF 简介 -`AOF(Append Only File)` 是以 **文本日志形式** 将 **所有写命令以 Redis 命令请求协议格式追加到 AOF 文件的末尾**,以此来记录数据的变化。**当服务器重启时,会重新载入和执行 AOF 文件中的命令,就可以恢复原始的数据**。AOF 适合作为 **热备**。 +`AOF(Append Only File)` 是将所有写命令追加写入“日志文件”,以此来记录数据的变化。当服务器重启时,会重新载入和执行 AOF 文件中的命令,就可以恢复原始的数据。AOF 适合作为**“热备”**。 AOF 可以通过 `appendonly yes` 配置选项来开启。 -命令请求会先保存到 AOF 缓冲区中,之后再定期写入并同步到 AOF 文件。 +### AOF 的优点和缺点 -#### AOF 的优点 +**AOF 的优点** - **如果系统发生故障,AOF 丢失数据比 RDB 少**。你可以使用不同的 fsync 策略:无 fsync;每秒 fsync;每次写的时候 fsync。使用默认的每秒 fsync 策略,Redis 的性能依然很好(fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失 1 秒的数据。 - **AOF 文件可修复** - AOF 文件是一个只进行追加的日志文件,所以不需要写入 seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用 redis-check-aof 工具修复这些问题。 - **AOF 文件可压缩**。Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。 - **AOF 文件可读** - AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 命令的格式保存。因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单。举个例子,如果你不小心执行了 FLUSHALL 命令,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis ,就可以将数据集恢复到 FLUSHALL 执行之前的状态。 -#### AOF 的缺点 +**AOF 的缺点** - **AOF 文件体积一般比 RDB 大** - 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。 - **恢复大数据集时,AOF 比 RDB 慢。** - 根据所使用的 fsync 策略,AOF 的速度可能会慢于快照。在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和快照一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,快照可以提供更有保证的最大延迟时间(latency)。 @@ -148,13 +210,17 @@ AOF 可以通过 `appendonly yes` 配置选项来开启。 AOF 的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。 - **命令追加** - 当 Redis 服务器开启 AOF 功能时,服务器在执行完一个写命令后,会以 Redis 命令协议格式将被执行的写命令追加到 AOF 缓冲区的末尾。 -- **文件写入**和**文件同步** - Redis 的服务器进程就是一个事件循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复。而时间事件则负责执行定时运行的函数。因为服务器在处理文件事件时可能会执行写命令,这些写命令会被追加到 AOF 缓冲区,服务器每次结束事件循环前,都会根据 `appendfsync` 选项来判断 AOF 缓冲区内容是否需要写入和同步到 AOF 文件中。 +- **文件写入**和**文件同步** + - Redis 的服务器进程就是一个事件循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复。而时间事件则负责执行想 `serverCron` 这样的定时运行的函数。 + - 因为服务器在处理文件事件时可能会执行写命令,这些写命令会被追加到 AOF 缓冲区,服务器每次结束事件循环前,都会根据 `appendfsync` 选项来判断 AOF 缓冲区内容是否需要写入和同步到 AOF 文件中。 `appendfsync` 不同选项决定了不同的持久化行为: -- **`always`** - 将缓冲区所有内容写入并同步到 AOF 文件。 -- **`everysec`** - 将缓冲区所有内容写入到 AOF 文件,如果上次同步 AOF 文件的时间距离现在超过一秒钟,那么再次对 AOF 文件进行同步,这个同步操作是有一个线程专门负责执行的。 -- **`no`** - 将缓冲区所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统决定。 +- **`always`** - 将 AOF 缓冲区中所有内容写入并同步到 AOF 文件。这种方式是最数据最安全的,但也是性能最差的。 +- **`no`** - 将 AOF 缓冲区所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统决定。这种方式是数据最不安全的,一旦出现故障,未来得及同步的所有数据都会丢失。 +- **`everysec`** - `appendfsync` 默认选项。将 AOF 缓冲区所有内容写入到 AOF 文件,如果上次同步 AOF 文件的时间距离现在超过一秒钟,那么再次对 AOF 文件进行同步,这个同步操作是有一个线程专门负责执行的。这张方式是前面两种的这种方案——性能足够好,且即使出现故障,仅丢失一秒钟内的数据。 + +`appendfsync` 选项的不同值对 AOF 持久化功能的安全性、以及 Redis 服务器的性能有很大的影响。 ### AOF 的载入 @@ -169,9 +235,7 @@ AOF 载入过程如下: 5. 循环执行步骤 3、4,直到所有写命令都被处理完毕为止。 6. 载入完毕。 -
- -
+![AOF 文件载入](https://raw.githubusercontent.com/dunwu/images/master/snap/202309171705818.png) ### AOF 的重写 @@ -182,9 +246,9 @@ AOF 载入过程如下: 为了解决 AOF 体积膨胀问题,Redis 提供了 AOF 重写功能,来对 AOF 文件进行压缩。**AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原来的 AOF 文件所保存的数据库状态一致,但体积更小**。 -AOF 重写并非读取和分析现有 AOF 文件的内容,而是直接从数据库中读取当前的数据库状态。即**依次读取数据库中的每个键值对,然后用一条命令去记录该键值对**,以此代替之前可能存在冗余的命令。 +AOF 重写并非读取和分析现有 AOF 文件的内容,而是直接从数据库中读取当前的数据库状态。即**从数据库中读取键的当前值,然后用一条命令去记录该键值对**,以此代替之前可能存在冗余的命令。 -#### AOF 后台重写 +### AOF 后台重写 作为一种辅助性功能,显然 Redis 并不想在 AOF 重写时阻塞 Redis 服务接收其他命令。因此,Redis 决定通过 `BGREWRITEAOF` 命令创建一个子进程,然后由子进程负责对 AOF 文件进行重写,这与 `BGSAVE` 原理类似。 @@ -192,7 +256,9 @@ AOF 重写并非读取和分析现有 AOF 文件的内容,而是直接从数 - 由于彼此不是在同一个进程中工作,AOF 重写不影响 AOF 写入和同步。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。 - 最后,服务器用新的 AOF 文件替换就的 AOF 文件,以此来完成 AOF 重写操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200130153716.png) +![BGREWRITEAOF 流程](https://raw.githubusercontent.com/dunwu/images/master/snap/202309171957918.png) + +> `BGREWRITEAOF` 命令的实现采用的是写时复制技术(Copy-On-Write,缩写为 CoW)。 可以通过设置 `auto-aof-rewrite-percentage` 和 `auto-aof-rewrite-min-size`,使得 Redis 在满足条件时,自动执行 `BGREWRITEAOF`。 @@ -230,7 +296,7 @@ AOF 持久化通过在 `redis.conf` 中的 `appendonly yes` 配置选项来开 - `auto-aof-rewrite-min-size` - AOF 重写文件的最小大小。 - `dir` - RDB 文件和 AOF 文件的存储路径。 -## 三、RDB 和 AOF +## RDB 和 AOF > 当 Redis 启动时, 如果 RDB 和 AOF 功能都开启了,那么程序会优先使用 AOF 文件来恢复数据集,因为 AOF 文件所保存的数据通常是最完整的。 @@ -256,7 +322,7 @@ AOF 持久化通过在 `redis.conf` 中的 `appendonly yes` 配置选项来开 执行的第二条命令用于关闭快照功能。 这一步是可选的, 如果你愿意的话, 也可以同时使用快照和 AOF 这两种持久化功能。 -> :bell: 重要:别忘了在 `redis.conf` 中打开 AOF 功能!否则的话,服务器重启之后,之前通过 CONFIG SET 设置的配置就会被遗忘,程序会按原来的配置来启动服务器。 +> 🔔 重要:别忘了在 `redis.conf` 中打开 AOF 功能!否则的话,服务器重启之后,之前通过 CONFIG SET 设置的配置就会被遗忘,程序会按原来的配置来启动服务器。 ### AOF 和 RDB 的相互作用 @@ -264,7 +330,15 @@ AOF 持久化通过在 `redis.conf` 中的 `appendonly yes` 配置选项来开 如果 `BGSAVE` 正在执行,并且用户显示地调用 `BGREWRITEAOF` 命令,那么服务器将向用户回复一个 OK 状态,并告知用户,`BGREWRITEAOF` 已经被预定执行。一旦 `BGSAVE` 执行完毕, `BGREWRITEAOF` 就会正式开始。 -## 四、Redis 备份 +### 混合持久化 + +Redis 4.0 提出了**混合使用 AOF 日志和内存快照**,也叫混合持久化,既保证了 Redis 重启速度,又降低数据丢失风险。 + +混合持久化工作在 **AOF 日志重写过程**,当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。 + +也就是说,使用了混合持久化,AOF 文件的**前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据**。 + +## Redis 备份 应该确保 Redis 数据有完整的备份。 @@ -282,18 +356,6 @@ Redis 的容灾备份基本上就是对数据进行备份,并将这些备份 容灾备份可以在 Redis 运行并产生快照的主数据中心发生严重的问题时,仍然让数据处于安全状态。 -## 五、要点总结 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224214047.png) - ## 参考资料 -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **教程** - - [Redis 命令参考](http://redisdoc.com/) \ No newline at end of file +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/13.Redis\344\272\213\344\273\266.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/13.Redis\344\272\213\344\273\266.md" new file mode 100644 index 0000000000..a65fe0f1f4 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/13.Redis\344\272\213\344\273\266.md" @@ -0,0 +1,104 @@ +--- +icon: logos:redis +title: Redis 事件 +date: 2023-09-11 22:22:32 +order: 13 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis +permalink: /pages/6e71c3/ +--- + +# Redis 事件 + +> Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: +> +> - **`文件事件(file event)`** - Redis 服务器通过套接字(Socket)与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。服务器与客户端(或其他的服务器)的通信会产生文件事件,而服务器通过监听并处理这些事件来完成一系列网络通信操作。 +> - **`时间事件(time event)`** - Redis 服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。 +> +> 关键词:`文件事件`、`时间事件` + +## 文件事件 + +Redis 基于 Reactor 模式开发了自己的网络时间处理器。 + +- Redis 文件事件处理器使用 I/O 多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。 +- 当被监听的套接字准备好执行连接应答、读取、写入、关闭操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。 + +虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器实现了高性能的网络通信模型。 + +文件事件处理器有四个组成部分:套接字、I/O 多路复用程序、文件事件分派器、事件处理器。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200130172525.png) + +## 时间事件 + +时间事件又分为: + +- **定时事件**:是让一段程序在指定的时间之内执行一次; +- **周期性事件**:是让一段程序每隔指定时间就执行一次。 + +Redis 将所有时间事件都放在一个无序链表中,每当时间事件执行器运行时,通过遍历整个链表查找出已到达的时间事件,并调用响应的事件处理器。 + +## 事件的调度与执行 + +服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能一直监听,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。 + +事件调度与执行由 aeProcessEvents 函数负责,伪代码如下: + +```python +def aeProcessEvents(): + + ## 获取到达时间离当前时间最接近的时间事件 + time_event = aeSearchNearestTimer() + + ## 计算最接近的时间事件距离到达还有多少毫秒 + remaind_ms = time_event.when - unix_ts_now() + + ## 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0 + if remaind_ms < 0: + remaind_ms = 0 + + ## 根据 remaind_ms 的值,创建 timeval + timeval = create_timeval_with_ms(remaind_ms) + + ## 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定 + aeApiPoll(timeval) + + ## 处理所有已产生的文件事件 + procesFileEvents() + + ## 处理所有已到达的时间事件 + processTimeEvents() +``` + +将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下: + +```python +def main(): + + ## 初始化服务器 + init_server() + + ## 一直处理事件,直到服务器关闭为止 + while server_is_not_shutdown(): + aeProcessEvents() + + ## 服务器关闭,执行清理操作 + clean_server() +``` + +从事件处理的角度来看,服务器运行流程如下: + +
+ +
+ +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/21.Redis\345\244\215\345\210\266.md" similarity index 93% rename from "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" rename to "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/21.Redis\345\244\215\345\210\266.md" index 8b64fc9f81..28d2e0cb09 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/05.Redis\345\244\215\345\210\266.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/21.Redis\345\244\215\345\210\266.md" @@ -1,6 +1,9 @@ --- +icon: logos:redis title: Redis 复制 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/20230914071554.png date: 2020-06-24 10:45:38 +order: 21 categories: - 数据库 - KV数据库 @@ -18,10 +21,10 @@ permalink: /pages/379cd8/ > 在 Redis 中,**可以通过执行 `SLAVEOF` 命令或设置 `slaveof` 选项,让一个服务器去复制(replicate)另一个服务器**,其中,后者叫主服务器(master),前者叫从服务器(slave)。 > > Redis 2.8 以前的复制不能高效处理断线后重复制的情况,而 Redis 2.8 新添的部分重同步可以解决这个问题。 +> +> 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`命令传播`、`心跳` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712182603.png) - -## 一、复制简介 +## 复制简介 Redis 通过 `slaveof host port` 命令来让一个服务器成为另一个服务器的从服务器。 @@ -38,7 +41,7 @@ Redis 通过 `slaveof host port` 命令来让一个服务器成为另一个服 - 只读模式由 `redis.conf` 文件中的 `slave-read-only` 选项控制, 也可以通过 [CONFIG SET parameter value](http://redisdoc.com/configure/config_set.html#config-set) 命令来开启或关闭这个模式。 - 只读从服务器会拒绝执行任何写命令, 所以不会出现因为操作失误而将数据不小心写入到了从服务器的情况。 -## 二、旧版复制 +## 旧版复制 > Redis 2.8 版本以前实现方式:`SYNC` 命令 @@ -56,7 +59,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate 3. 主服务器执行 `BGSAVE` 完毕后,主服务器会将生成的 RDB 文件发送给从服务器。从服务器接收并载入 RDB 文件,更新自己的数据库状态。 4. 主服务器将记录在缓冲区中的所有写命令发送给从服务器,从服务器执行这些写命令,更新自己的数据库状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224220353.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309172035716.png) ### 命令传播 @@ -71,13 +74,13 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate 对于初次复制,旧版复制功能可用很好完成任务;但是**对于断线后重复制,由于每次任然需要生成 RDB 并传输,效率很低**。 -> :bell: 注意:**SYNC 命令是一个非常耗费资源的操作。** +> 🔔 注意:**SYNC 命令是一个非常耗费资源的操作。** > > - 主服务器执行 `BGSAVE` 命令生成 RDB 文件,这个操作会耗费主服务器大量的 CPU、内存和磁盘 I/O 资源。 > - 主服务器传输 RDB 文件给从服务器,这个操作会耗费主从服务器大量的网络资源,并对主服务器响应时延产生影响。 > - 从服务器载入 RDB 文件期间,会阻塞其他命令请求。 -## 三、新版复制 +## 新版复制 > Redis 2.8 版本以后的新实现方式:使用 `PSYNC` 命令替代 `SYNC` 命令。 @@ -101,7 +104,7 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate - 如果主从服务器的复制偏移量相同,则说明二者的数据库状态一致; - 反之,则说明二者的数据库状态不一致。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-replication-offset.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309172031325.png) #### 复制积压缓冲区 @@ -148,9 +151,9 @@ Redis 的复制功能分为同步(sync)和命令传播(command propagate - 假如主从服务器的 **master run id 相同**,并且**指定的偏移量(offset)在内存缓冲区中还有效**,复制就会从上次中断的点开始继续。 - 如果其中一个条件不满足,就会进行完全重新同步(在 2.8 版本之前就是直接进行完全重新同步)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-psync-workflow.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309172030499.png) -## 四、心跳检测 +## 心跳检测 在**命令传播**阶段,从服务器默认会以**每秒一次**的频率,向主服务器发送命令: @@ -189,7 +192,7 @@ min-slaves-max-lag 10 如果因为网络故障,主服务传播给从服务器的写命令丢失,那么从服务器定时向主服务器发送 `REPLCONF ACK` 命令时,主服务器将发觉从服务器的复制偏移量少于自己的。然后,主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区中找到从服务器缺少的数据,并将这些数据重新发送给从服务器。 -## 五、复制的流程 +## 复制的流程 通过向从服务器发送如下 SLAVEOF 命令,可以让一个从服务器去复制一个主服务器。 @@ -260,7 +263,7 @@ REPLCONF ACK - 辅助实现 min-slave 选项。 - 检测命令丢失。 -## 六、复制的配置项 +## 复制的配置项 从 Redis 2.8 开始, 为了保证数据的安全性, 可以通过配置, 让主服务器只在有至少 N 个当前已连接从服务器的情况下, 才执行写命令。 @@ -287,12 +290,5 @@ REPLCONF ACK ## 参考资料 -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **教程** - - [Redis 命令参考](http://redisdoc.com/) \ No newline at end of file +- [Redis 官方文档之复制](https://redis.io/docs/management/replication/) +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/22.Redis\345\223\250\345\205\265.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/22.Redis\345\223\250\345\205\265.md" new file mode 100644 index 0000000000..35ea8718f1 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/22.Redis\345\223\250\345\205\265.md" @@ -0,0 +1,166 @@ +--- +icon: logos:redis +title: Redis 哨兵 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202309190748787.png +date: 2020-06-24 10:45:38 +order: 22 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 高可用 + - 选主 + - 故障转移 + - Raft +permalink: /pages/615afe/ +--- + +# Redis 哨兵 + +> Redis 2.8 版本,新增了哨兵模式,以支持“自动故障转移”,它是 Redis 的 HA 方案。 +> +> Redis 哨兵模式由一个或多个 Sentinel 实例组成 Sentinel 集群,可以监控任意多个主服务器,以及这些主服务器的所有从服务器;并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 +> +> 关键词:`高可用`、`监控`、`选主`、`故障转移`、`Raft` + +## 哨兵简介 + +Redis 的主从复制模式,虽然提供了一定程度的 **高可用性(High Availability)**。但是,当主节点出现故障时,只能通过手动操作将从节点晋升为主节点,这显然是比较低效的。为了解决这个问题,Redis 2.8 版本提供了哨兵模式(Sentinel)来支持“自动故障转移”。 + +Redis 哨兵模式由一个或多个 Sentinel 实例组成 Sentinel 集群,可以监控任意多个主服务器,以及这些主服务器的所有从服务器;并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309190749810.png) + +Sentinel 的主要功能如下: + +- **`监控(Monitoring)`** - Sentinel 不断检查主从服务器是否正常在工作。 +- **`通知(Notification)`** - Sentinel 可以通过一个 api 来通知系统管理员或者另外的应用程序,被监控的 Redis 实例有一些问题。 +- **`自动故障转移(Automatic Failover)`** - 如果一个主服务器下线,Sentinel 会开始自动故障转移:把一个从节点提升为主节点,并重新配置其他的从节点使用新的主节点,使用 Redis 服务的应用程序在连接的时候也被通知新的地址。 +- **`配置提供者(Configuration provider)`** - Sentinel 给客户端的服务发现提供来源:对于一个给定的服务,客户端连接到 Sentinels 来寻找当前主节点的地址。当故障转移发生的时候,Sentinel 将报告新的地址。 + +## 启动哨兵 + +启动一个 Sentinel 可以使用下面任意一条命令,两条命令效果完全相同。 + +```shell +redis-sentinel /path/to/sentinel.conf +redis-server /path/to/sentinel.conf --sentinel +``` + +当一个 Sentinel 启动时,它需要执行以下步骤: + +1. 初始化服务器。 +2. 使用 Sentinel 专用代码。 +3. 初始化 Sentinel 状态。 +4. 初始化 Sentinel 的主服务器列表。 +5. 创建连向被监视的主服务器的网络连接。 + +**Sentinel 本质上是一个运行在“特殊模式”下的 Redis 服务器**。Sentinel 模式下 Redis 服务器只支持 `PING`、`SENTINEL`、`INFO`、`SUBSCRIBE`、`UNSUBSCRIBE`、`PSUBSCRIBE`、`PUNSUBSCRIBE` 七个命令。 + +创建连向被监视的主服务器的网络连接,Sentinel 将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息。Sentinel 会读入用户指定的配置文件, 为每个要被监视的主服务器创建相应的实例结构, 并创建连向主服务器的命令连接和订阅连接: + +- **命令连接** - 专门用于向主服务器发送命令,并接受命令回复。 +- **订阅连接** - 专门用于订阅主服务器的 `__sentinel__:hello` 频道。 + +## 监控 + +### 获取服务器信息 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309190750857.png) + +默认情况下, Sentinel 以**“每十秒一次”**的频率向被监视的主服务器和从服务器**发送 `INFO` 命令**,并通过分析 `INFO` 命令的回复来获取服务器的当前信息。 + +- 主服务器 - 可以获取主服务器自身信息,以及其所属从服务器的地址信息。 +- 从服务器 - 从服务器自身信息,以及其主服务器的了解状态和地址。 + +**Sentinel 通过向主服务器发送 `INFO` 命令来获得主服务器属下所有从服务器的地址信息, 并为这些从服务器创建相应的实例结构, 以及连向这些从服务器的“命令连接”和“订阅连接”**。 + +对于监视同一个主服务器和从服务器的多个 Sentinel 来说, 它们会以“每两秒一次”的频率, 通过向被监视服务器的 `__sentinel__:hello` 频道发送消息来向其他 Sentinel 宣告自己的存在。Sentinel 只会与主服务器和从服务器创建命令连接和订阅连接, Sentinel 与 Sentinel 之间则只创建命令连接。 + +### 判断下线 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309190801360.png) + +#### 主观下线 + +**默认,每个 Sentinel 以“每秒一次”的频率,向它所知的“所有实例”发送一个 `PING` 命令**。 + +- “所知”是指,与 Sentinel 创建了命令连接的实例。 +- “所有实例”包括了主服务器、从服务器以及其他 Sentinel 实例。 + +如果,**某实例在指定的时长( `down-after-milliseconds` 设置的值,单位毫秒)中,未向 Sentinel 发送有效回复, Sentinel 会将该实例判定为“主观下线”**。 + +- 一个有效的 `PING` 回复可以是:`+PONG`、`-LOADING` 或者 `-MASTERDOWN`。如果服务器返回除以上三种回复之外的其他回复,又或者在 **指定时间** 内没有回复 `PING` 命令, 那么 Sentinel 认为服务器返回的回复无效。 +- “主观下线”适用于所有主节点和从节点。 + +#### 客观下线 + +当一个**“主服务器”**被 Sentinel 标记为**“主观下线”**后,为了确认其是否真的下线,Sentinel 会向同样监听该主服务器的其他 Sentinel 发起询问。如果有**“足够数量”**的 Sentinel 在指定的时间范围内认为主服务器已下线,那么这个**“主服务器”**被标记为**“客观下线”**。 + +- Sentinel 节点通过 `sentinel is-master-down-by-addr` 命令,向其它 Sentinel 节点询问对某主服务器的 **状态判断**。 +- “足够数量”是指 Sentinel 配置中 `quorum` 参数所设的值。 +- 客观下线只适用于主节点。 + +注:默认情况下, Sentinel 以**“每十秒一次”**的频率向被监视的主服务器和从服务器**发送 `INFO` 命令**。当一个主服务器被 Sentinel 标记为**“客观下线”**时,Sentinel 向该主服务器的所有从服务器发送 `INFO` 命令的频率,会从**“每十秒一次”**改为**“每秒一次”**。 + +## 选主 + +> Redis Sentinel 采用 [Raft 协议](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) 实现了其 Sentinel 选主流程。Raft 是一种共识性算法,想了解其原理,可以参考 [深入剖析共识性算法 Raft](https://dunwu.github.io/waterdrop/pages/4907dc/)。 + +**当一个“主服务器”被判断为“客观下线”时,监视该主服务器的各个 Sentinel 会进行“协商”,选举出一个领头的 Sentinel(Leader),并由领头 Sentinel 对下线主服务器执行“故障转移”操作**。 + +所有在线 Sentinel 都有资格被选为 Leader。 + +1. 当一个 Sentinel 认定某主服务器是“客观下线”后,该 Sentinel 会先看看自己是否投过票。 + - 如果已投票给其他 Sentinel 了,在 2 倍故障转移的超时时间内,都不能竞选 **Leader**——相当于它是一个 **Follower**。 + - 如果未投票,那么该 Sentinel 可以竞选 **Leader**,转为 **Candidate**。 +2. 如 Raft 协议所描述的,**Candidate** 需要完成几件事情: + 1. 更新故障转移状态为 start + 2. 将当前纪元(`epoch`) 加 1,表明开始新一轮的选举——这里的 `epoch` 相当于 Raft 协议中的 `term`。 + 3. 将自身的超时时间设为当前时间加上一个随机值,随机值为 1s 内的随机毫秒数。 + 4. 向其他节点发送 `is-master-down-by-addr` 命令,请求其他节点投票支持自己,命令会携带自己的 `epoch`。 + 5. Candidate 会投票给自己。在 Sentinel 中,投票的方式是把自己 `master` 结构体里的 `leader` 和 `leader_epoch` 改成投给的 Sentinel 和它的 `epoch`。 +3. 其他 Sentinel 收到 **Candidate** 的 `is-master-down-by-addr` 命令后,如果 Sentinel 当前 `epoch` 和 **Candidate** 传给他的 `epoch` 一样,说明他已经把自己 `master` 结构体里的 `leader` 和 `leader_epoch` 改成其他 **Candidate**,相当于把票投给了其他 **Candidate**。投票给其他 Sentinel 后,在当前 `epoch` 内,该 Sentinel 就只能成为 **Follower**。 +4. **Candidate** 会不断的统计自己的票数,如果满足“当选投票条件”,则该 **Candidate** 当选 **Leader**: + 1. 票数超过一半(监控主服务器的 Sentinel 的节点数的一半 + 1) + 2. 票数超过 Sentinel 配置的 `quorum` 参数——注:Raft 协议中没有这个限制,这是 Redis Sentinel 所独有的 +5. 如果在一个选举周期内(`epoch`),**Candidate** 没有满足“当选投票条件”(第 4 点描述的),则竞选失败。 +6. 如果在一个选举周期内(`epoch`),没有一个 **Candidate** 满足“当选投票条件”,说明所有 **Candidate** 都竞选失败,本轮选举作废。在等待超过 2 倍故障转移的超时时间后,开始新一轮的选举。 +7. 与 Raft 协议不同的是,Leader 并不会把自己成为 **Leader** 的消息发给其他 Sentinel。当 **Leader** 完成故障转移后,其他 Sentinel 检测到新的主服务器正常工作后,就会去掉“客观下线”的标识,从而不需要再发起选举。 + +## 故障转移 + +在选举产生出 Sentinel Leader 后,Sentinel Leader 将对已下线的主服务器执行故障转移操作。操作含以下三个步骤: + +(1)**选出新的主服务器** + +故障转移第一步,是 Sentinel Leader 在已下线主服务属下的所有从服务器中,挑选一个状态良好、数据完整的从服务器。然后,向这个从服务器发送 `SLAVEOF no one` 命令,将其转换为主服务器。 + +Sentinel Leader 如何选出新的主服务器: + +- 删除列表中所有处于下线或断线状态的从服务器。 +- 删除列表中所有最近五秒没有回复过 Sentinel Leader 的 `INFO` 命令的从服务器。 +- 删除所有与已下线主服务器连接断开超过 `down-after-milliseconds * 10` 毫秒的从服务器(`down-after-milliseconds` 指定了判断主服务器下线所需的时间)。 +- 之后, Sentinel Leader 先选出优先级最高的从服务器;如果优先级一样高,再选择复制偏移量最大的从服务器;如果结果还不唯一,则选出运行 ID 最小的从服务器。 + +(2)**修改从服务器的复制目标** + +选出新的主服务器后,Sentinel Leader 会向所有从服务器发送 `SLAVEOF` 命令,让它们去复制新的主服务器。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309190802685.png) + +(3)**将旧的主服务器变为从服务器** + +Sentinel Leader 将旧的主服务器标记为从服务器。当旧的主服务器重新上线,Sentinel 会向它发送 `SLAVEOF` 命令,让其成为从服务器。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309190803617.png) + +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) +- [渐进式解析 Redis 源码 - 哨兵 sentinel](http://www.web-lovers.com/redis-source-sentinel.html) +- [深入剖析 Redis 系列(二) - Redis 哨兵模式与高可用集群](https://juejin.im/post/5b7d226a6fb9a01a1e01ff64) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/23.Redis\351\233\206\347\276\244.md" similarity index 56% rename from "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" rename to "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/23.Redis\351\233\206\347\276\244.md" index 3d501a9a03..152afe7aea 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/07.Redis\351\233\206\347\276\244.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/23.Redis\351\233\206\347\276\244.md" @@ -1,6 +1,9 @@ --- +icon: logos:redis title: Redis 集群 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/20230914072642.png date: 2020-06-24 10:45:38 +order: 23 categories: - 数据库 - KV数据库 @@ -9,43 +12,56 @@ tags: - 数据库 - KV数据库 - Redis + - 高可用 + - 选主 + - 故障转移 - 集群 + - 分区 + - Raft + - Gossip permalink: /pages/77dfbe/ --- # Redis 集群 -> **[Redis 集群(Redis Cluster)](https://redis.io/topics/cluster-tutorial) 是 Redis 官方提供的分布式数据库方案**。 +> **[Redis 集群(Redis Cluster)](https://redis.io/topics/cluster-tutorial) 是 Redis 官方提供的“分布式数据库”方案**。 > -> 既然是分布式,自然具备分布式系统的基本特性:可扩展、高可用、一致性。 +> Redis Cluster 既然被设计分布式系统,自然需要具备分布式系统的基本特性:伸缩性、高可用、一致性。 > -> - Redis 集群通过划分 hash 槽来分区,进行数据分享。 -> - Redis 集群采用主从模型,提供复制和故障转移功能,来保证 Redis 集群的高可用。 -> - 根据 CAP 理论,Consistency、Availability、Partition tolerance 三者不可兼得,而 Redis 集群的选择是 AP。Redis 集群节点间采用异步通信方式,不保证强一致性,尽力达到最终一致性。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713100613.png) +> - **伸缩性** - Redis Cluster 通过划分虚拟 hash 槽来进行“分区”,以实现集群的伸缩性。 +> - **高可用** - Redis Cluster 采用主从架构,支持“复制”和“自动故障转移”,以保证 Redis Cluster 的高可用。 +> - **一致性** - 根据 CAP 理论,Consistency、Availability、Partition tolerance 三者不可兼得。而 Redis Cluster 的选择是 AP,即不保证“强一致性”,尽力达到“最终一致性”。 +> +> Redis Cluster 应用了 [Raft 协议](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) 协议和 Gossip 协议。 +> +> 关键词:`高可用`、`监控`、`选主`、`故障转移`、`分区`、`Raft`、`Gossip` -## 1. Redis Cluster 分区 +## Redis Cluster 分区 -### 1.1. 集群节点 +### 集群节点 -Redis 集群由多个节点组成,节点刚启动时,彼此是相互独立的。**节点通过握手( `CLUSTER MEET` 命令)来将其他节点添加到自己所处的集群中**。 +Redis Cluster 由多个节点组成,节点刚启动时,彼此是相互独立的。**节点通过握手( [`CLUSTER MEET`](https://redis.io/commands/cluster-meet/) 命令)来将其他节点添加到自己所处的集群中**。 -向一个节点发送 `CLUSTER MEET` 命令,可以让当前节点与指定 IP、PORT 的节点进行握手,握手成功时,当前节点会将指定节点加入所在集群。 +向一个节点发送 `CLUSTER MEET` 命令,可以让当前节点与指定 IP、PORT 的节点进行三次握手,握手成功时,当前节点会将指定节点加入所在集群。 **集群节点保存键值对以及过期时间的方式与单机 Redis 服务完全相同**。 -Redis 集群节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。 +Redis Cluster 节点分为主节点(master)和从节点(slave): + +- 主节点用于处理槽。 +- 从节点用于复制主节点, 并在主节点下线时, 代替主节点继续处理命令请求。 -### 1.2. 分配 Hash 槽 +### 分配 Hash 槽 -分布式存储需要解决的首要问题是把 **整个数据集** 按照 **分区规则** 映射到 **多个节点** 的问题,即把 **数据集** 划分到 **多个节点** 上,每个节点负责 **整体数据** 的一个 **子集**。 +分布式存储需要解决的首要问题是把整个数据集按照**“分区规则”** 到**多个节点**,即每个节点负责整体数据的一个 **子集**。 -**Redis 集群通过划分 hash 槽来将数据分区**。Redis 集群通过分区的方式来保存数据库的键值对:**集群的整个数据库被分为 16384 个哈希槽(slot)**,数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点可以处理 0 个或最多 16384 个槽。**如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态**。 +**Redis Cluster 将整个数据库规划为 “16384” 个虚拟的哈希槽**,数据库中的每个键都属于其中一个槽。**每个节点都会记录哪些槽指派给了自己, 而哪些槽又被指派给了其他节点**。 + +**如果数据库中有任何一个槽没有得到分配,那么集群处于“下线”状态**。 通过向节点发送 [`CLUSTER ADDSLOTS`](https://redis.io/commands/cluster-addslots) 命令,可以将一个或多个槽指派给节点负责。 -``` +```shell > CLUSTER ADDSLOTS 1 2 3 OK ``` @@ -56,14 +72,14 @@ OK - 节点B存储的哈希槽范围是:5501 – 11000 - 节点C存储的哈希槽范围是:11001 – 16384 -### 1.3. 寻址 +### 路由 当客户端向节点发送与数据库键有关的命令时,接受命令的节点会**计算出命令要处理的数据库属于哪个槽**,并**检查这个槽是否指派给了自己**: - 如果键所在的槽正好指派给了当前节点,那么当前节点直接执行命令。 -- 如果键所在的槽没有指派给当前节点,那么节点会向客户端返回一个 MOVED 错误,指引客户端重定向至正确的节点。 +- 如果键所在的槽没有指派给当前节点,那么节点会向客户端返回一个 `MOVED` 错误,指引客户端重定向至正确的节点。 -#### 1.3.1. 计算键属于哪个槽 +#### 计算键属于哪个槽 决定一个 key 应该分配到那个槽的算法是:**计算该 key 的 CRC16 结果再模 16834**。 @@ -77,96 +93,110 @@ HASH_SLOT = CRC16(KEY) mod 16384 clusterState.slots[i] == clusterState.myself ``` -#### 1.3.2. MOVED 错误 +#### MOVED 错误 -当节点发现键所在的槽并非自己负责处理的时候,节点就会向客户端返回一个 `MOVED` 错误,指引客户端转向正在负责槽的节点。 +节点在接到一个命令请求时,会先检查这个命令请求要处理的键所在的槽是否由自己负责, 如果不是的话, 节点将向客户端返回一个 `MOVED` 错误, `MOVED` 错误携带的信息可以指引客户端转向至正在负责相关槽的节点。 `MOVED` 错误的格式为: -``` +```shell MOVED : ``` -> 个人理解:MOVED 这种操作有点类似 HTTP 协议中的重定向。 +> 提示:`MOVED` 命令的作用有点类似 HTTP 协议中的重定向。 -### 1.4. 重新分区 +### 重新分区 -Redis 集群的**重新分区操作可以将任意数量的已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点**。 +对 Redis Cluster 的重新分片工作是由客户端(redis-trib)执行的, **重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点**。 -重新分区操作**可以在线进**行,在重新分区的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。 - -Redis 集群的重新分区操作由 Redis 集群管理软件 **redis-trib** 负责执行的,redis-trib 通过向源节点和目标节点发送命令来进行重新分区操作。 +重新分区操作可以**“在线”**进行,在重新分区的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。 重新分区的实现原理如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-cluster-trib.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-cluster-trib.png) + +### ASK 错误 + +如果节点 A 正在迁移槽 `i` 至节点 B , 那么当节点 A 没能在自己的数据库中找到命令指定的数据库键时, 节点 A 会向客户端返回一个 `ASK` 错误, 指引客户端到节点 B 继续查找指定的数据库键。 -### 1.5. ASK 错误 +`ASK` 错误与 `MOVED` 的区别在于: -`ASK` 错误与 `MOVED` 的区别在于:**ASK 错误只是两个节点在迁移槽的过程中使用的一种临时措施**,在客户端收到关于槽 X 的 ASK 错误之后,客户端只会在接下来的一次命令请求中将关于槽 X 的命令请求发送至 ASK 错误所指示的节点,但这种转向不会对客户端今后发送关于槽 X 的命令请求产生任何影响,客户端仍然会将关于槽 X 的命令请求发送至目前负责处理槽 X 的节点,除非 ASK 错误再次出现。 +- `MOVED` 错误表示槽的负责权已经从一个节点转移到了另一个节点; +- 而 `ASK` 错误只是两个节点在迁移槽的过程中使用的一种临时措施。 判断 ASK 错误的过程如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/redis/redis-ask.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/redis/redis-ask.png) -## 2. Redis Cluster 故障转移 +## Redis Cluster 复制 -### 2.1. 复制 +Redis Cluster 中的节点分为主节点和从节点,其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。 -Redis 复制机制可以参考:[Redis 复制](docs/05.KV数据库/01.Redis/05.Redis复制.md) +向一个节点发送命令 `CLUSTER REPLICATE ` 可以让接收命令的节点成为 node_id 所指定节点的从节点,并开始对主节点进行复制。 -### 2.2. 故障检测 +Redis Cluster 节点间的复制是“异步”的。 -**集群中每个节点都会定期向集群中的其他节点发送 PING 消息,以此来检测对方是否在线**。 +## Redis Cluster 故障转移 -节点的状态信息可以分为: +### 故障检测 -- 在线状态; +**集群中每个节点都会定期向集群中的其他节点发送 `PING` 消息,以此来检测对方是否在线**。 -- 下线状态(FAIL); +节点的状态信息可以分为: -- 疑似下线状态(PFAIL),即在规定的时间内,没有应答 PING 消息; +- 在线状态; +- 疑似下线状态(`PFAIL`) - 即在规定的时间内,没有应答 `PING` 消息 +- 已下线状态(`FAIL`) - 半数以上负责处理槽的主节点都将某个主节点视为“疑似下线”,则这个主节点将被标记为“已下线” -### 2.3. 故障转移 +### 故障转移 1. 下线主节点的所有从节点中,会有一个从节点被选中。 2. 被选中的从节点会执行 `SLAVEOF no one` 命令,成为新的主节点。 3. 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。 -4. 新的主节点向集群广播一条 PONG 消息,告知其他节点这个从节点已变成主节点。 +4. 新的主节点向集群广播一条 `PONG` 消息,告知其他节点这个从节点已变成主节点。 +5. 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。 + +### 选主 + +> Redis Sentinel 和 Redis Cluster 的选主流程非常相似,二者都基于[Raft 协议](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) 实现。 -### 2.4. 选举新的主节点 +1. 从节点发现自己的主节点状态为 `FAIL`。 +2. 从节点将自己记录的纪元(`epoch`)加 1,并广播消息,要求所有收到消息且有投票权的主节点都为自己投票。——这里的纪元(`epoch`),相当于 Raft 协议中的选期(`term`)。因个人习惯,后面统一将纪元描述为选期。 +3. 如果某主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票,那么主节点就返回一条确认消息,表示支持该从节点成为新的主节点。 +4. 每个参与选举的从节点都会根据收到的确认消息,统计自己所得的选票。 +5. 假设集群中存在 N 个具有投票权的主节点,那么**当某从节点得到“半数以上”(`N / 2 + 1`)的选票,则该从节点当选为新的主节点**。 +6. 由于每个选期中,任意具有投票权的主节点“只能投一票”,所以获得“半数以上”选票的从节点只能有一个。 +7. 如果在一个选期中,没有从节点能获得“半数以上”投票,则本次选期作废,开始进入下一个选期,直到选出新的主节点为止。 -Redis 集群选举新的主节点流程基于[共识算法:Raft](https://www.jianshu.com/p/8e4bbe7e276c) +## Redis Cluster 通信 -## 3. Redis Cluster 通信 +**集群中的节点通过发送和接收消息来进行通信**。Redis Cluster 各实例之间的通信方式采用 [Gossip 协议](http://publicatio.bibl.u-szeged.hu/1529/1/gossip11.pdf)来实现。 -集群中的节点通过发送和接收消息来进行通信。 +Redis Cluster 采用 Gossip 协议基于两个主要目标:**去中心化**以及**失败检测**。 -Redis 集群节点发送的消息主要有以下五种: +Redis Cluster 中,每个节点之间都会同步信息,但是每个节点的信息不保证实时的,即无法保证数据强一致性,但是保证**“数据最终一致性”**——当集群中发生节点增减、故障、主从关系变化、槽信息变更等事件时,通过不断的通信,在经过一段时间后,所有的节点都会同步集群全部节点的最新状态。 + +Redis Cluster 节点发送的消息主要有以下五种: - `MEET` - 请求接收方加入发送方所在的集群。 -- `PING` - 集群中每个节点每隔一段时间(默认为一秒)从已知节点列表中随机选出五个节点,然后对这五个节点中最久没联系的节点发送 PING 消息,以此检测被选中的节点是否在线。 -- `PONG` - 当接收方收到发送方发来的 MEET 消息或 PING 消息时,会返回一条 PONG 消息作为应答。 -- `FAIL` - 当一个主节点 A 判断另一个主节点 B 已经进入 FAIL 状态时,节点 A 会向集群广播一条关于节点 B 的 FAIL 消息,所有收到这条消息的节点都会立即将节点 B 标记为已下线。 -- `PUBLISH` - 当节点收到一个 PUBLISH 命令时,节点会执行这个命令,并向集群广播一条 PUBLISH 消息,所有接受到这条消息的节点都会执行相同的 PUBLISH 命令。 +- `PING` - 集群中每个节点每隔一段时间(默认为一秒)从已知节点列表中随机选出五个节点,然后对这五个节点中最久没联系的节点发送 `PING` 消息,以此检测被选中的节点是否在线。 +- `PONG` - 当接收方收到发送方发来的 `MEET` 消息或 `PING` 消息时,会返回一条 `PONG` 消息作为应答。 +- `FAIL` - 当一个主节点 A 判断另一个主节点 B 已经进入 `FAIL` 状态时,节点 A 会向集群广播一条关于节点 B 的 `FAIL` 消息,所有收到这条消息的节点都会立即将节点 B 标记为已下线。 +- `PUBLISH` - 当节点收到一个 `PUBLISH` 命令时,节点会执行这个命令,并向集群广播一条 `PUBLISH` 消息,所有接受到这条消息的节点都会执行相同的 `PUBLISH` 命令。 -## 4. Redis Cluster 应用 +## Redis Cluster 应用 -### 4.1. 集群功能限制 +### 集群功能限制 -Redis 集群相对 **单机**,存在一些功能限制,需要 **开发人员** 提前了解,在使用时做好规避。 +Redis Cluster 相对 **单机**,存在一些功能限制,需要 **开发人员** 提前了解,在使用时做好规避。 - `key` **批量操作** 支持有限:类似 `mset`、`mget` 操作,目前只支持对具有相同 `slot` 值的 `key` 执行 **批量操作**。对于 **映射为不同** `slot` 值的 `key` 由于执行 `mget`、`mget` 等操作可能存在于多个节点上,因此不被支持。 - - `key` **事务操作** 支持有限:只支持 **多** `key` 在 **同一节点上** 的 **事务操作**,当多个 `key` 分布在 **不同** 的节点上时 **无法** 使用事务功能。 - - `key` 作为 **数据分区** 的最小粒度,不能将一个 **大的键值** 对象如 `hash`、`list` 等映射到 **不同的节点**。 - - 不支持 **多数据库空间**:**单机** 下的 Redis 可以支持 `16` 个数据库(`db0 ~ db15`),**集群模式** 下只能使用 **一个** 数据库空间,即 `db0`。 - - **复制结构** 只支持一层:**从节点** 只能复制 **主节点**,不支持 **嵌套树状复制** 结构。 -### 4.2. 集群规模限制 +### 集群规模限制 Redis Cluster 的优点是易于使用。分区、主从复制、弹性扩容这些功能都可以做到自动化,通过简单的部署就可以获得一个大容量、高可靠、高可用的 Redis 集群,并且对于应用来说,近乎于是透明的。 @@ -178,9 +208,9 @@ Redis 的每个节点上,都保存了所有槽和节点的映射关系表, Gossip 协议的优点是去中心化;缺点是传播速度慢,并且是集群规模越大,传播的越慢。 -### 4.3. 集群配置 +### 集群配置 -我们后面会部署一个 Redis 集群作为例子,在那之前,先介绍一下集群在 redis.conf 中的参数。 +我们后面会部署一个 Redis Cluster 作为例子,在那之前,先介绍一下集群在 redis.conf 中的参数。 - **cluster-enabled** `` - 如果配置”yes”则开启集群功能,此 redis 实例作为集群的一个节点,否则,它是一个普通的单一的 redis 实例。 - **cluster-config-file** `` - 注意:虽然此配置的名字叫“集群配置文件”,但是此配置文件不能人工编辑,它是集群节点自动维护的文件,主要用于记录集群中有哪些节点、他们的状态以及一些持久化参数等,方便在重启时恢复这些状态。通常是在收到请求之后这个文件就会被更新。 @@ -189,7 +219,7 @@ Gossip 协议的优点是去中心化;缺点是传播速度慢,并且是集 - **cluster-migration-barrier** `` - 主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移。更详细介绍可以看本教程后面关于副本迁移到部分。 - **cluster-require-full-coverage** `` - 在部分 key 所在的节点不可用时,如果此参数设置为”yes”(默认值), 则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的 key 提供读操作。 -## 5. 其他 Redis 集群方案 +## 其他 Redis 集群方案 Redis Cluster 不太适合用于大规模集群,所以,如果要构建超大 Redis 集群,需要选择替代方案。一般有三种方案类型: @@ -197,17 +227,16 @@ Redis Cluster 不太适合用于大规模集群,所以,如果要构建超大 - 代理分区方案 - 查询路由方案 -### 5.1. 客户端分区方案 +### 客户端分区方案 **客户端** 就已经决定数据会被 **存储** 到哪个 Redis 节点或者从哪个 Redis 节点 **读取数据**。其主要思想是采用 **哈希算法** 将 Redis 数据的 `key` 进行散列,通过 `hash` 函数,特定的 `key`会 **映射** 到特定的 Redis 节点上。 **客户端分区方案** 的代表为 Redis Sharding,Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的 Redis **多实例集群** 方法。Java 的 Redis 客户端驱动库 [**Jedis**](https://github.com/redis/jedis),支持 Redis Sharding 功能,即 ShardedJedis 以及 **结合缓存池** 的 ShardedJedisPool。 - **优点**:不使用 **第三方中间件**,**分区逻辑** 可控,**配置** 简单,节点之间无关联,容易 **线性扩展**,灵活性强。 - - **缺点**:**客户端** 无法 **动态增删** 服务节点,客户端需要自行维护 **分发逻辑**,客户端之间 **无连接共享**,会造成 **连接浪费**。 -### 5.2. 代理分区方案 +### 代理分区方案 **客户端** 发送请求到一个 **代理组件**,**代理** 解析 **客户端** 的数据,并将请求转发至正确的节点,最后将结果回复给客户端。 @@ -216,7 +245,7 @@ Redis Cluster 不太适合用于大规模集群,所以,如果要构建超大 **代理分区** 主流实现的有方案有 **[Twemproxy](https://github.com/twitter/twemproxy)** 和 [**Codis**](https://github.com/CodisLabs/codis)。 -#### 5.2.1. Twemproxy +#### Twemproxy **[Twemproxy](https://github.com/twitter/twemproxy)** 也叫 `nutcraker`,是 Twitter 开源的一个 Redis 和 Memcache 的 **中间代理服务器** 程序。 @@ -225,37 +254,21 @@ Redis Cluster 不太适合用于大规模集群,所以,如果要构建超大 - **优点**:应用范围广,稳定性较高,中间代理层 **高可用**。 - **缺点**:无法平滑地 **水平扩容/缩容**,无 **可视化管理界面**,运维不友好,出现故障,不能 **自动转移**。 -#### 5.2.2. Codis +#### Codis [**Codis**](https://github.com/CodisLabs/codis) 是一个 **分布式** Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接 **原生的** Redis-Server 没有的区别。[**Codis**](https://github.com/CodisLabs/codis) 底层会 **处理请求的转发**,不停机的进行 **数据迁移** 等工作。[**Codis**](https://github.com/CodisLabs/codis) 采用了无状态的 **代理层**,对于 **客户端** 来说,一切都是透明的。 - **优点**:实现了上层 Proxy 和底层 Redis 的 **高可用**,**数据分区** 和 **自动平衡**,提供 **命令行接口** 和 RESTful API,提供 **监控** 和 **管理** 界面,可以动态 **添加** 和 **删除** Redis 节点。 - - **缺点**:**部署架构** 和 **配置** 复杂,不支持 **跨机房** 和 **多租户**,不支持 **鉴权管理**。 -### 5.3. 查询路由方案 +### 查询路由方案 **客户端随机地** 请求任意一个 Redis 实例,然后由 Redis 将请求 **转发** 给 **正确** 的 Redis 节点。Redis Cluster 实现了一种 **混合形式** 的 **查询路由**,但并不是 **直接** 将请求从一个 Redis 节点 **转发** 到另一个 Redis 节点,而是在 **客户端** 的帮助下直接 **重定向**( `redirected`)到正确的 Redis 节点。 - **优点**:**去中心化**,数据按照 **槽** 存储分布在多个 Redis 实例上,可以平滑的进行节点 **扩容/缩容**,支持 **高可用** 和 **自动故障转移**,运维成本低。 - - **缺点**:重度依赖 Redis-trib 工具,缺乏 **监控管理**,需要依赖 Smart Client (**维护连接**,**缓存路由表**,`MultiOp` 和 `Pipeline` 支持)。Failover 节点的 **检测过慢**,不如有 **中心节点** 的集群及时(如 ZooKeeper)。Gossip 消息采用广播方式,集群规模越大,开销越大。无法根据统计区分 **冷热数据**。 -## 6. 参考资料 - -- **官网** - - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) - - [Redis 官方文档中文版](http://redis.cn/) -- **中间件** - - [Twemproxy](https://github.com/twitter/twemproxy) - - [Codis](https://github.com/CodisLabs/codis) -- **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) -- **教程** - - [后端存储实战课](https://time.geekbang.org/column/intro/100046801) -- **文章** - - [Redis 集群教程](http://ifeve.com/redis-cluster-tutorial/) - - [Redis 集群的原理和搭建](https://www.jianshu.com/p/c869feb5581d) - - [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) \ No newline at end of file +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) +- [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/31.Redis\345\217\221\345\270\203\350\256\242\351\230\205.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/31.Redis\345\217\221\345\270\203\350\256\242\351\230\205.md" new file mode 100644 index 0000000000..989c3e3dee --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/31.Redis\345\217\221\345\270\203\350\256\242\351\230\205.md" @@ -0,0 +1,137 @@ +--- +icon: logos:redis +title: Redis 发布订阅 +date: 2023-09-11 22:22:30 +order: 31 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 订阅 + - 观察者模式 +permalink: /pages/a329e5/ +--- + +# Redis 发布订阅 + +> Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。 +> +> Redis 有两种发布订阅模式 +> +> - 基于频道(Channel)的发布订阅 +> - 基于模式(Pattern)的发布订阅 +> +> 关键词:`订阅`、`SUBSCRIBE`、`PSUBSCRIBE`、`PUBLISH`、`观察者模式` + +## 观察者模式 + +Redis 发布订阅应用了设计模式中经典的“观察者模式”。 + +**观察者模式**(Observer)是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。 + +- 当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时,可使用观察者模式。 +- 当应用中的一些对象必须观察其他对象时,可使用该模式。但仅能在有限时间内或特定情况下使用。 + +![观察者模式](https://d1.awsstatic.com/product-marketing/Messaging/sns_img_topic.e024462ec88e79ed63d690a2eed6e050e33fb36f.png) + +## Redis 订阅模式 + +Redis 有两种发布订阅模式: + +(1)**基于频道(Channel)的发布订阅** + +服务器状态在 `pubsub_channels` 字典保存了所有频道的订阅关系: `SUBSCRIBE` 命令负责将客户端和被订阅的频道关联到这个字典里面, 而 `UNSUBSCRIBE` 命令则负责解除客户端和被退订频道之间的关联。 + +【示例】订阅指定频道示例 + +打开客户端一,执行以下命令 + +```shell +> SUBSCRIBE first second +Reading messages... (press Ctrl-C to quit) +1) "subscribe" +2) "first" +3) (integer) 1 +1) "subscribe" +2) "second" +3) (integer) 2 +``` + +打开客户端二,执行以下命令 + +```shell +> PUBLISH second Hello +1) "1" +``` + +此时,客户端一会收到以下内容 + +```shell +1) "message" +2) "second" +3) "Hello" +``` + +(2)**基于模式(Pattern)的发布订阅** + +服务器状态在 `pubsub_patterns` 链表保存了所有模式的订阅关系: `PSUBSCRIBE` 命令负责将客户端和被订阅的模式记录到这个链表中, 而 `UNSUBSCRIBE` 命令则负责移除客户端和被退订模式在链表中的记录。 + +【示例】订阅符合指定模式的频道 + +打开客户端一,执行以下命令 + +```shell +> PSUBSCRIBE news.* +Reading messages... (press Ctrl-C to quit) +1) "psubscribe" +2) "news.*" +3) (integer) 1 +``` + +打开客户端二,执行以下命令 + +```shell +> PUBLISH news.A Hello +1) "1" +``` + +打开客户端三,执行以下命令 + +```shell +> PUBLISH news.B World +1) "1" +``` + +此时,客户端一会收到以下内容 + +```shell +1) "pmessage" +2) "news.*" +3) "news.A" +4) "Hello" +1) "pmessage" +2) "news.*" +3) "news.B" +4) "World" +``` + +## 发布订阅命令 + +Redis 提供了以下与订阅发布有关的命令: + +| 命令 | 描述 | +| -------------------------------------------------------- | -------------------------- | +| [`SUBSCRIBE`](https://redis.io/commands/subscribe/) | 订阅指定频道 | +| [`UNSUBSCRIBE`](https://redis.io/commands/unsubscribe) | 取消订阅指定频道 | +| [`PSUBSCRIBE`](https://redis.io/commands/psubscribe) | 订阅符合指定模式的频道 | +| [`PUNSUBSCRIBE`](https://redis.io/commands/punsubscribe) | 取消订阅符合指定模式的频道 | +| [`PUBLISH`](https://redis.io/commands/publish/) | 发送信息到指定的频道 | +| [`PUBSUB`](https://redis.io/commands/pubsub/) | 查看发布订阅状态 | + +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/32.Redis\344\272\213\345\212\241.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/32.Redis\344\272\213\345\212\241.md" new file mode 100644 index 0000000000..2d206daf41 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/32.Redis\344\272\213\345\212\241.md" @@ -0,0 +1,175 @@ +--- +icon: logos:redis +title: Redis 事务 +date: 2020-01-30 21:48:57 +order: 32 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 事务 + - ACID +permalink: /pages/476a09/ +--- + +# Redis 事务 + +> **Redis 仅支持“非严格”的事务**。所谓“非严格”是指:Redis 事务保证“全部执行命令”;但是,Redis 事务“不支持回滚”。 +> +> 关键词:`事务`、`ACID`、`MULTI`、`EXEC`、`DISCARD`、`WATCH` + +## Redis 事务简介 + +### 什么是 ACID + +ACID 是数据库事务正确执行的四个基本要素。 + +- **原子性(Atomicity)** + - 事务被视为不可分割的最小单元,事务中的所有操作**要么全部提交成功,要么全部失败回滚**。 + - 回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 +- **一致性(Consistency)** + - 数据库在事务执行前后都保持一致性状态。 + - 在一致性状态下,所有事务对一个数据的读取结果都是相同的。 +- **隔离性(Isolation)** + - 一个事务所做的修改在最终提交以前,对其它事务是不可见的。 +- **持久性(Durability)** + - 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 + - 可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 + +**一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。** + +- 只有满足一致性,事务的执行结果才是正确的。 +- 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。 +- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 +- 事务满足持久化是为了能应对系统崩溃的情况。 + +![ACID](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库ACID.png) + +### Redis 事务的特性 + +Redis 的事务总是支持 ACID 中的原子性、一致性和隔离性, 当服务器运行在 AOF 持久化模式下, 并且 `appendfsync` 选项的值为 `always` 时, 事务也具有持久性。 + +但需要注意的是:**Redis 仅支持“非严格”的事务**。这里的“非严格”,其实指的是 Redis 事务只能部分保证 ACID 中的原子性。 + +- **Redis 事务保证全部执行命令** - Redis 事务中的多个命令会被打包到事务队列中,然后按先进先出(FIFO)的顺序执行。事务在执行过程中不会被中断,当事务队列中的所有命令都被执行完毕之后,事务才会结束。 +- **Redis 事务不支持回滚** - 如果命令执行失败不会回滚,而是会继续执行下去。 + +Redis 官方的[事务特性文档](https://redis.io/docs/interact/transactions/)给出的不支持回滚的理由是: + +- Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面。 +- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 + +## Redis 事务应用 + +[`MULTI`](https://redis.io/commands/multi)、[`EXEC`](https://redis.io/commands/exec)、[`DISCARD`](https://redis.io/commands/discard) 和 [`WATCH`](https://redis.io/commands/watch) 是 Redis 事务相关的命令。 + +事务可以一次执行多个命令, 并且有以下两个重要的保证: + +- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 +- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 + +### MULTI + +**[`MULTI`](https://redis.io/commands/multi) 命令用于开启一个事务,它总是返回 OK 。** + +`MULTI` 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC 命令被调用时, 所有队列中的命令才会被执行。 + +以下是一个事务例子, 它原子地增加了 `foo` 和 `bar` 两个键的值: + +```python +> MULTI +OK +> INCR foo +QUEUED +> INCR bar +QUEUED +> EXEC +1) (integer) 1 +2) (integer) 1 +``` + +### EXEC + +**[`EXEC`](https://redis.io/commands/exec) 命令负责触发并执行事务中的所有命令。** + +- 如果客户端在使用 `MULTI` 开启了一个事务之后,却因为断线而没有成功执行 `EXEC` ,那么事务中的所有命令都不会被执行。 +- 另一方面,如果客户端成功在开启事务之后执行 `EXEC` ,那么事务中的所有命令都会被执行。 + +`MULTI` 和 `EXEC` 中的操作将会一次性发送给服务器,而不是一条一条发送,这种方式称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。 + +### DISCARD + +**当执行 [`DISCARD`](https://redis.io/commands/discard) 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出。** + +示例: + +```python +> SET foo 1 +OK +> MULTI +OK +> INCR foo +QUEUED +> DISCARD +OK +> GET foo +"1" +``` + +### WATCH + +**[`WATCH`](https://redis.io/commands/watch) 命令可以为 Redis 事务提供 check-and-set (CAS)行为。** + +被 `WATCH` 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 `EXEC` 执行之前被修改了, 那么整个事务都会被取消, `EXEC` 返回 `nil-reply` 来表示事务已经失败。 + +```python +WATCH mykey +val = GET mykey +val = val + 1 +MULTI +SET mykey $val +EXEC +``` + +使用上面的代码, 如果在 `WATCH` 执行之后, `EXEC` 执行之前, 有其他客户端修改了 `mykey` 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。 + +这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。 + +`WATCH` 使得 `EXEC` 命令需要有条件地执行:事务只能在所有被监视键都没有被修改的前提下执行,如果这个前提不能满足的话,事务就不会被执行。 + +`WATCH` 命令可以被调用多次。对键的监视从 `WATCH` 执行之后开始生效,直到调用 `EXEC` 为止。 + +用户还可以在单个 `WATCH` 命令中监视任意多个键,例如: + +```python +redis> WATCH key1 key2 key3 +OK +``` + +#### 取消 WATCH 的场景 + +当 `EXEC` 被调用时, 不管事务是否成功执行, 对所有键的监视都会被取消。另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。 + +使用无参数的 `UNWATCH` 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 `UNWATCH` 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。 + +#### 使用 WATCH 创建原子操作 + +`WATCH` 可以用于创建 Redis 没有内置的原子操作。 + +举个例子,以下代码实现了原创的 `ZPOP` 命令,它可以原子地弹出有序集合中分值(`score`)最小的元素: + +```python +WATCH zset +element = ZRANGE zset 0 0 +MULTI +ZREM zset element +EXEC +``` + +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/33.Redis\347\256\241\351\201\223.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/33.Redis\347\256\241\351\201\223.md" new file mode 100644 index 0000000000..55c62adfc4 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/33.Redis\347\256\241\351\201\223.md" @@ -0,0 +1,127 @@ +--- +icon: logos:redis +title: Redis 管道 +date: 2023-09-11 22:22:31 +order: 33 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - Pipeline +permalink: /pages/f1bbae/ +--- + +# Redis 管道 + +> 关键词:`Pipeline` + +## Pipeline 简介 + +Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。通常情况下,一个 Redis 命令的请求、响应遵循以下步骤: + +- 客户端向服务端发送一个查询请求,并监听 Socket 返回(通常是以阻塞模式,等待服务端响应)。 +- 服务端处理命令,并将结果返回给客户端。 + +显然,如果每个 Redis 命令都发起一次请求、响应,会很低效。因此,Redis 客户端提供了一种批量处理技术,即 + +**管道技术(`Pipeline`)**。Pipeline 的工作原理就是:**将多个 Redis 命令一次性发送给服务端,服务端处理后,统一返回给客户端**。由于减少了通信次数,自然提升了处理效率。 + +![](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7268887661/p514690.jpg) + +## Pipeline 限制 + +在使用 Redis 管道技术时,要注意一些限制,避免踩坑: + +- **Pipeline 不能保证原子性** - Pipeline 只是将客户端发送命令的方式改为批量发送,而服务端在接收到 Pipeline 发来的命令后,将其拆解为一条条命令,然后依然是串行执行。执行过程中,服务端有可能执行其他客户端的命令,所以无法保证原子性。如需保证原子性,可以考虑使用事务或 Lua 脚本。 +- **Pipeline 不支持回滚** - Pipeline 没有事务的特性,如果待执行命令的前后存在依赖关系,请勿使用 Pipeline。 +- **Pipeline 命令不宜过大** - 使用管道发送命令时,Redis Server 会将部分请求放到缓存队列中(占用内存),执行完毕后一次性发送结果。如果需要发送大量的命令,会占用大量的内存,因此应该按照合理数量分批次的处理。 +- **Pipeline 不支持跨 slot 访问** - 由于 Pipeline 不支持跨 slot 访问,因此,在 Redis 集群模式下使用 Pipeline 时要确保访问的 key 都在同一 slot 中。 + +## Pipeline 案例 + +主流的 Redis 客户端,一般都会支持管道技术。 + +【示例】Jedis 管道使用示例 + +```java +public class Demo { + + public static void main(String[] args) { + + String host = "localhost"; + int port = 6379; + Jedis jedis = new Jedis(host, port); + + String key = "pipeline:test"; + jedis.del(key); + + // -------- 方法1 + method1(jedis, key); + + //-------- 方法2 + method2(jedis, key); + } + + private static void method2(Jedis jedis, String key) { + System.out.println("-----方法2-----"); + jedis.del(key);//初始化 + Pipeline pipeline = jedis.pipelined(); + //需要先声明Response + Response r1 = pipeline.incr(key); + System.out.println("Pipeline发送请求"); + Response r2 = pipeline.incr(key); + System.out.println("Pipeline发送请求"); + Response r3 = pipeline.incr(key); + System.out.println("Pipeline发送请求"); + Response r4 = pipeline.incr(key); + System.out.println("Pipeline发送请求"); + Response r5 = pipeline.incr(key); + System.out.println("Pipeline发送请求"); + try { + // 此时还未开始接收响应,所以此操作会出错 + r1.get(); + } catch (Exception e) { + System.out.println(" <<< Pipeline error:还未开始接收响应 >>> "); + } + // 发送请求完成,开始接收响应 + System.out.println("发送请求完成,开始接收响应"); + pipeline.sync(); + System.out.println("Pipeline 接收响应 Response: " + r1.get()); + System.out.println("Pipeline 接收响应 Response: " + r2.get()); + System.out.println("Pipeline 接收响应 Response: " + r3.get()); + System.out.println("Pipeline 接收响应 Response: " + r4.get()); + System.out.println("Pipeline 接收响应 Response: " + r5.get()); + jedis.close(); + } + + private static void method1(Jedis jedis, String key) { + Pipeline pipeline = jedis.pipelined(); + System.out.println("-----方法1-----"); + for (int i = 0; i < 5; i++) { + pipeline.incr(key); + System.out.println("Pipeline 发送请求"); + } + // 发送请求完成,开始接收响应 + System.out.println("发送请求完成,开始接收响应"); + List responses = pipeline.syncAndReturnAll(); + if (responses == null || responses.isEmpty()) { + jedis.close(); + throw new RuntimeException("Pipeline error: 没有接收到响应"); + } + for (Object resp : responses) { + System.out.println("Pipeline 接收响应 Response: " + resp.toString()); + } + System.out.println(); + } + +} +``` + +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) +- [阿里云管道传输](https://help.aliyun.com/zh/redis/use-cases/use-pipelining-to-batch-issue-commands?spm=a2c4g.11186623.0.0.1c193393SEIu92) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/34.Redis\350\204\232\346\234\254.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/34.Redis\350\204\232\346\234\254.md" new file mode 100644 index 0000000000..86e378e02e --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/34.Redis\350\204\232\346\234\254.md" @@ -0,0 +1,70 @@ +--- +icon: logos:redis +title: Redis 脚本 +date: 2020-01-30 21:48:57 +order: 34 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - Lua +permalink: /pages/30456b/ +--- + +# Redis 脚本 + +> Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。 +> +> 关键词:`Lua` + +## 为什么使用 Lua + +Lua 是一种轻量小巧的脚本语言,用标准 C 语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。 + +在 Redis 中,执行单一命令是原子性操作,所以不会出现并发问题。但有的业务场景下,需要执行多个命令,同时确保不出现并发问题,这就需要用到 Lua 脚本了。 + +**Redis 执行 Lua 是原子操作**。因为 Redis 使用串行化的方式来执行 Redis 命令, 所以在任何特定时间里, 最多都只会有一个脚本能够被放进 Lua 环境里面运行, 因此, 整个 Redis 服务器只需要创建一个 Lua 环境即可。 + +由于,Redis 执行 Lua 具有原子性,所以常被用于需要原子性执行多命令的场景。 + +## Redis 脚本命令 + +| 命令 | 说明 | +| --------------- | -------------------------------------------------------------------------------------------------------------------- | +| `EVAL` | `EVAL` 命令为客户端输入的脚本在 Lua 环境中定义一个函数, 并通过调用这个函数来执行脚本。 | +| `EVALSHA` | `EVALSHA` 命令通过直接调用 Lua 环境中已定义的函数来执行脚本。 | +| `SCRIPT_FLUSH` | `SCRIPT_FLUSH` 命令会清空服务器 `lua_scripts` 字典中保存的脚本, 并重置 Lua 环境。 | +| `SCRIPT_EXISTS` | `SCRIPT_EXISTS` 命令接受一个或多个 SHA1 校验和为参数, 并通过检查 `lua_scripts` 字典来确认校验和对应的脚本是否存在。 | +| `SCRIPT_LOAD` | `SCRIPT_LOAD` 命令接受一个 Lua 脚本为参数, 为该脚本在 Lua 环境中创建函数, 并将脚本保存到 `lua_scripts` 字典中。 | +| `SCRIPT_KILL` | `SCRIPT_KILL` 命令用于停止正在执行的脚本。 | + +## Redis 执行 Lua 的工作流程 + +为了在 Redis 服务器中执行 Lua 脚本, Redis 在服务器内嵌了一个 Lua 环境(environment), 并对这个 Lua 环境进行了一系列修改, 从而确保这个 Lua 环境可以满足 Redis 服务器的需要。 + +Redis 服务器创建并修改 Lua 环境的整个过程由以下步骤组成: + +1. 创建一个基础的 Lua 环境, 之后的所有修改都是针对这个环境进行的。 +2. 载入多个函数库到 Lua 环境里面, 让 Lua 脚本可以使用这些函数库来进行数据操作。 +3. 创建全局表格 `redis` , 这个表格包含了对 Redis 进行操作的函数, 比如用于在 Lua 脚本中执行 Redis 命令的 `redis.call` 函数。 +4. 使用 Redis 自制的随机函数来替换 Lua 原有的带有副作用的随机函数, 从而避免在脚本中引入副作用。 +5. 创建排序辅助函数, Lua 环境使用这个辅佐函数来对一部分 Redis 命令的结果进行排序, 从而消除这些命令的不确定性。 +6. 创建 `redis.pcall` 函数的错误报告辅助函数, 这个函数可以提供更详细的出错信息。 +7. 对 Lua 环境里面的全局环境进行保护, 防止用户在执行 Lua 脚本的过程中, 将额外的全局变量添加到了 Lua 环境里面。 +8. 将完成修改的 Lua 环境保存到服务器状态的 `lua` 属性里面, 等待执行服务器传来的 Lua 脚本。 + +## Redis 执行 Lua 的要点 + +- Redis 服务器专门使用一个伪客户端来执行 Lua 脚本中包含的 Redis 命令。 +- Redis 使用脚本字典来保存所有被 `EVAL` 命令执行过, 或者被 `SCRIPT_LOAD` 命令载入过的 Lua 脚本, 这些脚本可以用于实现 `SCRIPT_EXISTS` 命令, 以及实现脚本复制功能。 +- 服务器在执行脚本之前, 会为 Lua 环境设置一个超时处理钩子, 当脚本出现超时运行情况时, 客户端可以通过向服务器发送 `SCRIPT_KILL` 命令来让钩子停止正在执行的脚本, 或者发送 `SHUTDOWN nosave` 命令来让钩子关闭整个服务器。 +- 主服务器复制 `EVAL` 、 `SCRIPT_FLUSH` 、 `SCRIPT_LOAD` 三个命令的方法和复制普通 Redis 命令一样 —— 只要将相同的命令传播给从服务器就可以了。 +- 主服务器在复制 `EVALSHA` 命令时, 必须确保所有从服务器都已经载入了 `EVALSHA` 命令指定的 SHA1 校验和所对应的 Lua 脚本, 如果不能确保这一点的话, 主服务器会将 `EVALSHA` 命令转换成等效的 `EVAL` 命令, 并通过传播 `EVAL` 命令来获得相同的脚本执行效果。 + +## 参考资料 + +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/41.Redis\350\277\220\347\273\264.md" similarity index 99% rename from "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" rename to "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/41.Redis\350\277\220\347\273\264.md" index 64cb3ed5ee..c6ae9382a8 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/20.Redis\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/41.Redis\350\277\220\347\273\264.md" @@ -1,6 +1,8 @@ --- +icon: logos:redis title: Redis 运维 date: 2020-06-24 10:45:38 +order: 41 categories: - 数据库 - KV数据库 @@ -19,7 +21,7 @@ permalink: /pages/537098/ > > SET 操作每秒钟 110000 次;GET 操作每秒钟 81000 次。 -## 一、Redis 安装 +## Redis 安装 ### Window 下安装 @@ -116,7 +118,7 @@ sh redis-install.sh [version] [port] [password] - `port` - redis 服务端口号 - `password` - 访问密码 -## 二、Redis 单机使用和配置 +## Redis 单机使用和配置 ### 启动 Redis @@ -252,7 +254,7 @@ SET: 403063.28 requests per second GET: 508388.41 requests per second ``` -## 三、Redis 集群使用和配置 +## Redis 集群使用和配置 Redis 3.0 后支持集群模式。 @@ -619,13 +621,13 @@ rebalance:表明让 Redis 自动根据节点数进行均衡哈希槽分配。 --cluster-use-empty-masters:表明 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712125827.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712125827.png) 执行结束后,查看状态: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712130234.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200712130234.png) -## 四、Redis 命令 +## Redis 命令 ### 通用命令 @@ -660,13 +662,13 @@ rebalance:表明让 Redis 自动根据节点数进行均衡哈希槽分配。 - `cluster countkeysinslot ` - 返回槽 slot 目前包含的键值对数量。 - `cluster getkeysinslot ` - 返回 count 个 slot 槽中的键。 -#### 重新分片 +### 重新分片 添加节点:./redis-cli --cluster add-node 192.168.1.136:7007 192.168.1.136:7001 --cluster-slave redis-cli --cluster reshard 172.22.6.3 7001 -## 五、客户端 +## 客户端 推荐使用 [**RedisDesktopManager**](https://github.com/uglide/RedisDesktopManager) @@ -674,7 +676,7 @@ redis-cli --cluster reshard 172.22.6.3 7001 - **官网** - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) + - [Redis Github](https://github.com/antirez/redis) - [Redis 官方文档中文版](http://redis.cn/) - **书籍** - [《Redis 实战》](https://item.jd.com/11791607.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/42.Redis\345\256\236\346\210\230.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/42.Redis\345\256\236\346\210\230.md" new file mode 100644 index 0000000000..d11963ff2f --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/42.Redis\345\256\236\346\210\230.md" @@ -0,0 +1,506 @@ +--- +icon: logos:redis +title: Redis 实战 +date: 2020-06-24 10:45:38 +order: 42 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis +permalink: /pages/1fc9c4/ +--- + +# Redis 实战 + +## 缓存 + +缓存是 Redis 最常见的应用场景。 + +Redis 有多种数据类型,以及丰富的操作命令,并且有着高性能、高可用的特性,非常适合用于分布式缓存。 + +> 缓存应用的基本原理,请参考 [**缓存基本原理**](https://dunwu.github.io/design/distributed/分布式缓存.html) 第四 ~ 第六节内容。 + +## BitMap 和 BloomFilter + +Redis 除了 5 种基本数据类型外,还支持 BitMap 和 BloomFilter(即布隆过滤器,可以通过 Redis Module 支持)。 + +BitMap 和 BloomFilter 都可以用于解决缓存穿透问题。要点在于:过滤一些不可能存在的数据。 + +> 什么是缓存穿透,可以参考:[**缓存基本原理**](https://dunwu.github.io/design/distributed/分布式缓存.html) + +小数据量可以用 BitMap,大数据量可以用布隆过滤器。 + +## 分布式锁 + +使用 Redis 作为分布式锁,基本要点如下: + +- **互斥性** - 使用 `setnx` 抢占锁。 +- **避免永远不释放锁** - 使用 `expire` 加一个过期时间,避免一直不释放锁,导致阻塞。 +- **原子性** - setnx 和 expire 必须合并为一个原子指令,避免 setnx 后,机器崩溃,没来得及设置 expire,从而导致锁永不释放。 + +> 更多分布式锁的实现方式及细节,请参考:[分布式锁基本原理](https://dunwu.github.io/waterdrop/pages/40ac64/) + +根据 Redis 的特性,在实际应用中,存在一些应用小技巧。 + +## keys 和 scan + +使用 `keys` 指令可以扫出指定模式的 key 列表。 + +如果这个 redis 正在给线上的业务提供服务,那使用 `keys` 指令会有什么问题? + +首先,Redis 是单线程的。`keys` 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。 + +这个时候可以使用 `scan` 指令,`scan` 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 `keys` 指令长。 + +不过,增量式迭代命令也不是没有缺点的: 举个例子, 使用 `SMEMBERS` 命令可以返回集合键当前包含的所有元素, 但是对于 `SCAN` 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。 + +## 大 Key 如何处理 + +> 什么是 Redis 大 key? + +大 key 并不是指 key 的值很大,而是 key 对应的 value 很大。 + +一般而言,下面这两种情况被称为大 key: + +- String 类型的值大于 10 KB; +- Hash、List、Set、ZSet 类型的元素的个数超过 5000 个; + +> 大 key 会造成什么问题? + +大 key 会带来以下四种影响: + +- **客户端超时阻塞**。由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。 +- **引发网络阻塞**。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。 +- **阻塞工作线程**。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。 +- **内存分布不均**。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。 + +> 如何找到大 key ? + +**_1、redis-cli --bigkeys 查找大 key_** + +可以通过 redis-cli --bigkeys 命令查找大 key: + +```shell +redis-cli -h 127.0.0.1 -p 6379 -a "password" -- bigkeys +``` + +使用的时候注意事项: + +- 最好选择在从节点上执行该命令。因为主节点上执行时,会阻塞主节点; +- 如果没有从节点,那么可以选择在 Redis 实例业务压力的低峰阶段进行扫描查询,以免影响到实例的正常运行;或者可以使用 -i 参数控制扫描间隔,避免长时间扫描降低 Redis 实例的性能。 + +该方式的不足之处: + +- 这个方法只能返回每种类型中最大的那个 bigkey,无法得到大小排在前 N 位的 bigkey; +- 对于集合类型来说,这个方法只统计集合元素个数的多少,而不是实际占用的内存量。但是,一个集合中的元素个数多,并不一定占用的内存就多。因为,有可能每个元素占用的内存很小,这样的话,即使元素个数有很多,总内存开销也不大; + +**_2、使用 SCAN 命令查找大 key_** + +使用 SCAN 命令对数据库扫描,然后用 TYPE 命令获取返回的每一个 key 的类型。 + +对于 String 类型,可以直接使用 STRLEN 命令获取字符串的长度,也就是占用的内存空间字节数。 + +对于集合类型来说,有两种方法可以获得它占用的内存大小: + +- 如果能够预先从业务层知道集合元素的平均大小,那么,可以使用下面的命令获取集合元素的个数,然后乘以集合元素的平均大小,这样就能获得集合占用的内存大小了。List 类型:`LLEN` 命令;Hash 类型:`HLEN` 命令;Set 类型:`SCARD` 命令;Sorted Set 类型:`ZCARD` 命令; +- 如果不能提前知道写入集合的元素大小,可以使用 `MEMORY USAGE` 命令(需要 Redis 4.0 及以上版本),查询一个键值对占用的内存空间。 + +**_3、使用 RdbTools 工具查找大 key_** + +使用 RdbTools 第三方开源工具,可以用来解析 Redis 快照(RDB)文件,找到其中的大 key。 + +比如,下面这条命令,将大于 10 kb 的 key 输出到一个表格文件。 + +```shell +rdb dump.rdb -c memory --bytes 10240 -f redis.csv +``` + +> 如何删除大 key? + +删除操作的本质是要释放键值对占用的内存空间,不要小瞧内存的释放过程。 + +释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序。 + +所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞,如果主线程发生了阻塞,其他所有请求可能都会超时,超时越来越多,会造成 Redis 连接耗尽,产生各种异常。 + +因此,删除大 key 这一个动作,我们要小心。具体要怎么做呢?这里给出两种方法: + +- 分批次删除 +- 异步删除(Redis 4.0 版本以上) + +**_1、分批次删除_** + +对于**删除大 Hash**,使用 `hscan` 命令,每次获取 100 个字段,再用 `hdel` 命令,每次删除 1 个字段。 + +Python 代码: + +```python +def del_large_hash(): + r = redis.StrictRedis(host='redis-host1', port=6379) + large_hash_key ="xxx" #要删除的大hash键名 + cursor = '0' + while cursor != 0: + # 使用 hscan 命令,每次获取 100 个字段 + cursor, data = r.hscan(large_hash_key, cursor=cursor, count=100) + for item in data.items(): + # 再用 hdel 命令,每次删除1个字段 + r.hdel(large_hash_key, item[0]) +``` + +对于**删除大 List**,通过 `ltrim` 命令,每次删除少量元素。 + +Python 代码: + +```python +def del_large_list(): + r = redis.StrictRedis(host='redis-host1', port=6379) + large_list_key = 'xxx' #要删除的大list的键名 + while r.llen(large_list_key)>0: + #每次只删除最右100个元素 + r.ltrim(large_list_key, 0, -101) +``` + +对于**删除大 Set**,使用 `sscan` 命令,每次扫描集合中 100 个元素,再用 `srem` 命令每次删除一个键。 + +Python 代码: + +```python +def del_large_set(): + r = redis.StrictRedis(host='redis-host1', port=6379) + large_set_key = 'xxx' # 要删除的大set的键名 + cursor = '0' + while cursor != 0: + # 使用 sscan 命令,每次扫描集合中 100 个元素 + cursor, data = r.sscan(large_set_key, cursor=cursor, count=100) + for item in data: + # 再用 srem 命令每次删除一个键 + r.srem(large_size_key, item) +``` + +对于**删除大 ZSet**,使用 `zremrangebyrank` 命令,每次删除 top 100 个元素。 + +Python 代码: + +```python +def del_large_sortedset(): + r = redis.StrictRedis(host='large_sortedset_key', port=6379) + large_sortedset_key='xxx' + while r.zcard(large_sortedset_key)>0: + # 使用 zremrangebyrank 命令,每次删除 top 100个元素 + r.zremrangebyrank(large_sortedset_key,0,99) +``` + +**_2、异步删除_** + +从 Redis 4.0 版本开始,可以采用**异步删除**法,**用 unlink 命令代替 del 来删除**。 + +这样 Redis 会将这个 key 放入到一个异步线程中进行删除,这样不会阻塞主线程。 + +除了主动调用 unlink 命令实现异步删除之外,我们还可以通过配置参数,达到某些条件的时候自动进行异步删除。 + +主要有 4 种场景,默认都是关闭的: + +```text +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del +noslave-lazy-flush no +``` + +它们代表的含义如下: + +- lazyfree-lazy-eviction:表示当 Redis 运行内存超过 maxmeory 时,是否开启 lazy free 机制删除; +- lazyfree-lazy-expire:表示设置了过期时间的键值,当过期之后是否开启 lazy free 机制删除; +- lazyfree-lazy-server-del:有些指令在处理已存在的键时,会带有一个隐式的 del 键的操作,比如 rename 命令,当目标键已存在,Redis 会先删除目标键,如果这些目标键是一个 big key,就会造成阻塞删除的问题,此配置表示在这种场景中是否开启 lazy free 机制删除; +- slave-lazy-flush:针对 slave (从节点) 进行全量数据同步,slave 在加载 master 的 RDB 文件前,会运行 flushall 来清理自己的数据,它表示此时是否开启 lazy free 机制删除。 + +建议开启其中的 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del 等配置,这样就可以有效的提高主线程的执行效率。 + +## 最受欢迎文章 + +选出最受欢迎文章,需要支持对文章进行评分。 + +### 对文章进行投票 + +(1)使用 HASH 存储文章 + +使用 `HASH` 类型存储文章信息。其中:key 是文章 ID;field 是文章的属性 key;value 是属性对应值。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225143038.jpg) + +操作: + +- 存储文章信息 - 使用 `HSET` 或 `HMGET` 命令 +- 查询文章信息 - 使用 `HGETALL` 命令 +- 添加投票 - 使用 `HINCRBY` 命令 + +(2)使用 `ZSET` 针对不同维度集合排序 + +使用 `ZSET` 类型分别存储按照时间排序和按照评分排序的文章 ID 集合。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225145742.jpg) + +操作: + +- 添加记录 - 使用 `ZADD` 命令 +- 添加分数 - 使用 `ZINCRBY` 命令 +- 取出多篇文章 - 使用 `ZREVRANGE` 命令 + +(3)为了防止重复投票,使用 `SET` 类型记录每篇文章 ID 对应的投票集合。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225150105.jpg) + +操作: + +- 添加投票者 - 使用 `SADD` 命令 +- 设置有效期 - 使用 `EXPIRE` 命令 + +(4)假设 user:115423 给 article:100408 投票,分别需要高更新评分排序集合以及投票集合。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225150138.jpg) + +当需要对一篇文章投票时,程序需要用 ZSCORE 命令检查记录文章发布时间的有序集合,判断文章的发布时间是否超过投票有效期(比如:一星期)。 + +```java + public void articleVote(Jedis conn, String user, String article) { + // 计算文章的投票截止时间。 + long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS; + + // 检查是否还可以对文章进行投票 + // (虽然使用散列也可以获取文章的发布时间, + // 但有序集合返回的文章发布时间为浮点数, + // 可以不进行转换直接使用)。 + if (conn.zscore("time:", article) < cutoff) { + return; + } + + // 从article:id标识符(identifier)里面取出文章的ID。 + String articleId = article.substring(article.indexOf(':') + 1); + + // 如果用户是第一次为这篇文章投票,那么增加这篇文章的投票数量和评分。 + if (conn.sadd("voted:" + articleId, user) == 1) { + conn.zincrby("score:", VOTE_SCORE, article); + conn.hincrBy(article, "votes", 1); + } + } +``` + +### 发布并获取文章 + +发布文章: + +- 添加文章 - 使用 `INCR` 命令计算新的文章 ID,填充文章信息,然后用 `HSET` 命令或 `HMSET` 命令写入到 `HASH` 结构中。 +- 将文章作者 ID 添加到投票名单 - 使用 `SADD` 命令添加到代表投票名单的 `SET` 结构中。 +- 设置投票有效期 - 使用 `EXPIRE` 命令设置投票有效期。 + +```java + public String postArticle(Jedis conn, String user, String title, String link) { + // 生成一个新的文章ID。 + String articleId = String.valueOf(conn.incr("article:")); + + String voted = "voted:" + articleId; + // 将发布文章的用户添加到文章的已投票用户名单里面, + conn.sadd(voted, user); + // 然后将这个名单的过期时间设置为一周(第3章将对过期时间作更详细的介绍)。 + conn.expire(voted, ONE_WEEK_IN_SECONDS); + + long now = System.currentTimeMillis() / 1000; + String article = "article:" + articleId; + // 将文章信息存储到一个散列里面。 + HashMap articleData = new HashMap(); + articleData.put("title", title); + articleData.put("link", link); + articleData.put("user", user); + articleData.put("now", String.valueOf(now)); + articleData.put("votes", "1"); + conn.hmset(article, articleData); + + // 将文章添加到根据发布时间排序的有序集合和根据评分排序的有序集合里面。 + conn.zadd("score:", now + VOTE_SCORE, article); + conn.zadd("time:", now, article); + + return articleId; + } +``` + +分页查询最受欢迎文章: + +使用 `ZINTERSTORE` 命令根据页码、每页记录数、排序号,根据评分值从大到小分页查出文章 ID 列表。 + +```java + public List> getArticles(Jedis conn, int page, String order) { + // 设置获取文章的起始索引和结束索引。 + int start = (page - 1) * ARTICLES_PER_PAGE; + int end = start + ARTICLES_PER_PAGE - 1; + + // 获取多个文章ID。 + Set ids = conn.zrevrange(order, start, end); + List> articles = new ArrayList<>(); + // 根据文章ID获取文章的详细信息。 + for (String id : ids) { + Map articleData = conn.hgetAll(id); + articleData.put("id", id); + articles.add(articleData); + } + + return articles; + } +``` + +### 对文章进行分组 + +如果文章需要分组,功能需要分为两块: + +- 记录文章属于哪个群组 +- 负责取出群组里的文章 + +将文章添加、删除群组: + +```java + public void addRemoveGroups(Jedis conn, String articleId, String[] toAdd, String[] toRemove) { + // 构建存储文章信息的键名。 + String article = "article:" + articleId; + // 将文章添加到它所属的群组里面。 + for (String group : toAdd) { + conn.sadd("group:" + group, article); + } + // 从群组里面移除文章。 + for (String group : toRemove) { + conn.srem("group:" + group, article); + } + } +``` + +取出群组里的文章: + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200225214210.jpg) + +- 通过对存储群组文章的集合和存储文章评分的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章评分排序的群组文章。 +- 通过对存储群组文章的集合和存储文章发布时间的有序集合执行 `ZINTERSTORE` 命令,可以得到按照文章发布时间排序的群组文章。 + +```java + public List> getGroupArticles(Jedis conn, String group, int page, String order) { + // 为每个群组的每种排列顺序都创建一个键。 + String key = order + group; + // 检查是否有已缓存的排序结果,如果没有的话就现在进行排序。 + if (!conn.exists(key)) { + // 根据评分或者发布时间,对群组文章进行排序。 + ZParams params = new ZParams().aggregate(ZParams.Aggregate.MAX); + conn.zinterstore(key, params, "group:" + group, order); + // 让Redis在60秒钟之后自动删除这个有序集合。 + conn.expire(key, 60); + } + // 调用之前定义的getArticles函数来进行分页并获取文章数据。 + return getArticles(conn, page, key); + } +``` + +## 管理令牌 + +网站一般会以 Cookie、Session、令牌这类信息存储用户身份信息。 + +可以将 Cookie/Session/令牌 和用户的映射关系存储在 `HASH` 结构。 + +下面以令牌来举例。 + +### 查询令牌 + +```java + public String checkToken(Jedis conn, String token) { + // 尝试获取并返回令牌对应的用户。 + return conn.hget("login:", token); + } +``` + +### 更新令牌 + +- 用户每次访问页面,可以记录下令牌和当前时间戳的映射关系,存入一个 `ZSET` 结构中,以便分析用户是否活跃,继而可以周期性清理最老的令牌,统计当前在线用户数等行为。 +- 用户如果正在浏览商品,可以记录到用户最近浏览过的商品有序集合中(集合可以限定数量,超过数量进行裁剪),存入到一个 `ZSET` 结构中,以便分析用户最近可能感兴趣的商品,以便推荐商品。 + +```java + public void updateToken(Jedis conn, String token, String user, String item) { + // 获取当前时间戳。 + long timestamp = System.currentTimeMillis() / 1000; + // 维持令牌与已登录用户之间的映射。 + conn.hset("login:", token, user); + // 记录令牌最后一次出现的时间。 + conn.zadd("recent:", timestamp, token); + if (item != null) { + // 记录用户浏览过的商品。 + conn.zadd("viewed:" + token, timestamp, item); + // 移除旧的记录,只保留用户最近浏览过的25个商品。 + conn.zremrangeByRank("viewed:" + token, 0, -26); + conn.zincrby("viewed:", -1, item); + } + } +``` + +### 清理令牌 + +上一节提到,更新令牌时,将令牌和当前时间戳的映射关系,存入一个 `ZSET` 结构中。所以可以通过排序得知哪些令牌最老。如果没有清理操作,更新令牌占用的内存会不断膨胀,直到导致机器宕机。 + +比如:最多允许存储 1000 万条令牌信息,周期性检查,一旦发现记录数超出 1000 万条,将 ZSET 从新到老排序,将超出 1000 万条的记录清除。 + +```java +public static class CleanSessionsThread extends Thread { + + private Jedis conn; + + private int limit; + + private volatile boolean quit; + + public CleanSessionsThread(int limit) { + this.conn = new Jedis("localhost"); + this.conn.select(15); + this.limit = limit; + } + + public void quit() { + quit = true; + } + + @Override + public void run() { + while (!quit) { + // 找出目前已有令牌的数量。 + long size = conn.zcard("recent:"); + // 令牌数量未超过限制,休眠并在之后重新检查。 + if (size <= limit) { + try { + sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + continue; + } + + // 获取需要移除的令牌ID。 + long endIndex = Math.min(size - limit, 100); + Set tokenSet = conn.zrange("recent:", 0, endIndex - 1); + String[] tokens = tokenSet.toArray(new String[tokenSet.size()]); + + // 为那些将要被删除的令牌构建键名。 + ArrayList sessionKeys = new ArrayList(); + for (String token : tokens) { + sessionKeys.add("viewed:" + token); + } + + // 移除最旧的那些令牌。 + conn.del(sessionKeys.toArray(new String[sessionKeys.size()])); + conn.hdel("login:", tokens); + conn.zrem("recent:", tokens); + } + } + +} +``` + +## 参考资料 + +- [《Redis 实战》](https://item.jd.com/11791607.html) +- [《Redis 设计与实现》](https://item.jd.com/11486101.html) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/99.Redis\351\235\242\350\257\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/99.Redis\351\235\242\350\257\225.md" new file mode 100644 index 0000000000..62a9517cc0 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/99.Redis\351\235\242\350\257\225.md" @@ -0,0 +1,750 @@ +--- +icon: logos:redis +title: Redis 面试 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202309231131433.png +date: 2020-07-13 17:03:42 +order: 99 +categories: + - 数据库 + - KV数据库 + - Redis +tags: + - 数据库 + - KV数据库 + - Redis + - 面试 +permalink: /pages/451b73/ +--- + +# Redis 面试 + +## Redis 简介 + +### 什么是 Redis + +【问题】 + +- 什么是 Redis? +- Redis 有什么功能和特性? + +【解答】 + +什么是 Redis: + +**Redis 是一个开源的“内存”数据库**。由于,Redis 的读写操作都是在内存中完成,因此其**读写速度非常快**。 + +- **高性能** - 由于,Redis 的读写操作都是在内存中完成,因此性能极高。 +- **高并发** - Redis 单机 QPS 能达到 10w+,将近是 Mysql 的 10 倍。 + +Redis 常被用于**缓存,消息队列、分布式锁等场景**。 + +Redis 的功能和特性: + +- **Redis 支持多种数据类型**。如:String(字符串)、Hash(哈希)、 List (列表)、Set(集合)、Zset(有序集合)、Bitmaps(位图)、HyperLogLog(基数统计)、GEO(地理空间)、Stream(流)。 +- **Redis 的读写采用“单线程”模型**,因此,其操作天然就具有**原子性**。 +- Redis 支持两种持久化策略:RDB 和 AOF。 + +- Redis 有多种高可用方案:**主从复制**模式、**哨兵**模式、**集群**模式。 + +- Redis 支持很多丰富的特性,如:**事务** 、**Lua 脚本**、**发布订阅**、**过期删除**、**内存淘汰**等等。 + +![](https://architecturenotes.co/content/images/size/w2400/2022/08/Redis-v2-01-1.jpg) + +图来自 https://architecturenotes.co/redis/ + +### Redis 有哪些应用场景 + +【问题】 + +- Redis 有哪些应用场景? + +【解答】 + +- **缓存** - 将热点数据放到内存中,设置内存的最大使用量以及过期淘汰策略来保证缓存的命中率。 +- **计数器** - Redis 这种内存数据库能支持计数器频繁的读写操作。 +- **应用限流** - 限制一个网站访问流量。 +- **消息队列** - 使用 List 数据类型,它是双向链表。 +- **查找表** - 使用 HASH 数据类型。 +- **聚合运算** - 使用 SET 类型,例如求两个用户的共同好友。 +- **排行榜** - 使用 ZSET 数据类型。 +- **分布式 Session** - 多个应用服务器的 Session 都存储到 Redis 中来保证 Session 的一致性。 +- **分布式锁** - 除了可以使用 SETNX 实现分布式锁之外,还可以使用官方提供的 RedLock 分布式锁实现。 + +### Redis vs. Memcached + +【问题】 + +- Redis 和 Memcached 有什么相同点? +- Redis 和 Memcached 有什么差异? +- 分布式缓存技术选型,选 Redis 还是 Memcached,为什么? + +【解答】 + +Redis 与 Memcached 的**共性**: + +1. 都是内存数据库,因此性能都很高 +2. 都有过期策略。 + +因为以上两点,所以常被作为缓存使用。 + +Redis 与 Memcached 的**差异**: + +| | Redis | Memcached | +| -------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| 数据类型 | 支持多种数据类型:String、Hash、List、Set、ZSet 等 | 只支持 String 类型 | +| 持久化 | 支持两种持久化策略:RDB 和 AOF | 不支持持久化,一旦重启或宕机就会丢失数据 | +| 分布式 | 支持分布式 | 本身不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点 | +| 线程模型 | 读写采用单线程+IO 多路复用。因此存储小数据时比 Memcached 性能更高 | 采用多线程+IO 多路复用。在 100k 以上的数据中,Memcached 性能要高于 Redis | +| 其他功能 | 支持发布订阅模型、Lua 脚本、事务等功能 | 不支持 | + +通过以上分析,可以看出,Redis 在很多方面都占有优势。因此,绝大多数情况下,优先选择 Redis 作为分布式缓存。 + +> 参考:[《脚踏两只船的困惑 - Memcached 与 Redis》](www.imooc.com/article/23549) + +### Redis 为什么快 + +【问题】 + +- Redis 有多快? +- Redis 为什么这么快? + +【解答】 + +根据 [Redis 官方 Benchmark](https://redis.io/docs/management/optimization/benchmarks/) 文档的描述,Redis 单机 QPS 能达到 10w+。 + +![Redis 官方 Benchmark QPS 图](https://redis.io/docs/management/optimization/benchmarks/Connections_chart.png) + +Redis 是单线程模型(Redis 6.0 已经支持多线程模型),为什么还能有这么高的并发? + +- **Redis 读写基于内存** +- **IO 多路复用** + **读写单线程模型** + - IO 多路复用是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。 + - 单线程模型避免了由于并发而产生的线程切换、锁竞争等开销。 + - 由于,Redis 读写基于内存,性能很高,所以 CPU 并不是制约 Redis 性能表现的瓶颈所在。更多情况下是受到内存大小和网络 I/O 的限制,所以 Redis 核心网络模型使用单线程并没有什么问题。 +- **高效的数据结构** + +![](https://pbs.twimg.com/media/FoYNzdcacAAMjy5?format=jpg&name=4096x4096) + +图来自 [Why is redis so fast?](https://blog.bytebytego.com/p/why-is-redis-so-fast) + +## Redis 数据类型 + +### Redis 支持哪些数据类型 + +【问题】 + +- Redis 支持哪些数据类型? + +【解答】 + +- Redis 支持五种基本数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。 +- 随着 Redis 版本升级,又陆续支持以下数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309232155082.png) + +### Redis 各数据类型的应用场景 + +【问题】 + +Redis 各数据类型有哪些应用场景? + +【解答】 + +- **String(字符串)** - 缓存对象、分布式 Session、分布式锁、计数器、限流器、分布式 ID 等。 +- **Hash(哈希)** - 缓存对象、购物车等。 +- **List(列表)** - 消息队列 +- **Set(集合)** - 聚合计算(并集、交集、差集),如点赞、共同关注、抽奖活动等。 +- **Zset(有序集合)** - 排序场景,如排行榜、电话和姓名排序等。 + +- **BitMap**(2.2 版新增) - 二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等; +- **HyperLogLog**(2.8 版新增) - 海量数据基数统计的场景,比如百万级网页 UV 计数等; +- **GEO**(3.2 版新增) - 存储地理位置信息的场景,比如滴滴叫车; +- **Stream**(5.0 版新增) - 消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息 ID,支持以消费组形式消费数据。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309232144470.jpg) + +### Redis 基本数据类型的底层实现 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309241112034.png) + +- **String 类型** - String 类型的底层数据结构是 SDS。SDS 是 Redis 针对字符串类型的优化,具有以下特性: + - 常数复杂度获取字符串长度 + - 杜绝缓冲区溢出 + - 减少修改字符串长度时所需的内存重分配次数 +- **List 类型** - 列表对象的编码可以是 `ziplist` 或者 `linkedlist` 。当列表对象可以同时满足以下两个条件时, 列表对象使用 `ziplist` 编码;否则,使用 `linkedlist` 编码。 + - 列表对象保存的所有字符串元素的长度都小于 `64` 字节; + - 列表对象保存的元素数量小于 `512` 个; +- **Hash 类型** - 哈希对象的编码可以是 `ziplist` 或者 `hashtable` 。当哈希对象同时满足以下两个条件时, 使用 `ziplist` 编码;否则,使用 `hashtable` 编码。 + - 哈希对象保存的所有键值对的键和值的字符串长度都小于 `64` 字节; + - 哈希对象保存的键值对数量小于 `512` 个; +- **Set 类型** - 集合对象的编码可以是 `intset` 或者 `hashtable` 。当集合对象可以同时满足以下两个条件时,集合对象使用 `intset` 编码;否则,使用 `hashtable` 编码。 + - 集合对象保存的所有元素都是整数值; + - 集合对象保存的元素数量不超过 `512` 个; +- **Zset 类型** - 有序集合的编码可以是 `ziplist` 或者 `skiplist` 。当有序集合对象可以同时满足以下两个条件时,有序集合对象使用 `ziplist` 编码;否则,使用 `skiplist` 编码。 + - 有序集合保存的元素数量小于 `128` 个; + - 有序集合保存的所有元素成员的长度都小于 `64` 字节; + +## Redis 过期删除和内存淘汰 + +### Redis 过期删除策略 + +【问题】 + +- Redis 的过期删除策略是什么? + +【解答】 + +Redis 采用的过期策略是:**定期删除+惰性删除**。 + +- **定时删除** - 在设置 key 的过期时间的同时,创建一个定时器,让定时器在 key 的过期时间来临时,立即执行 key 的删除操作。 + - 优点 - 保证过期 key 被尽可能快的删除,释放内存。 + - 缺点 - **如果过期 key 较多,可能会占用相当一部分的 CPU,从而影响服务器的吞吐量和响应时延**。 +- **惰性删除** - 放任 key 过期不管,但是每次访问 key 时,都检查 key 是否过期,如果过期的话,就删除该 key ;如果没有过期,就返回该 key。 + - 优点 - 占用 CPU 最少。程序只会在读写键时,对当前键进行过期检查,因此不会有额外的 CPU 开销。 + - 缺点 - **过期的 key 可能因为没有被访问,而一直无法释放,造成内存的浪费,有内存泄漏的风险**。 +- **定期删除** - 每隔一段时间,程序就对数据库进行一次检查,删除里面的过期 key 。至于要删除多少过期 key ,以及要检查多少个数据库,则由算法决定。定期删除是前两种策略的一种折中方案。定期删除策略的难点是删除操作执行的时长和频率。 + - 执行太频或执行时间过长,就会出现和定时删除相同的问题; + - 执行太少或执行时间过短,就会出现和惰性删除相同的问题; + +### 持久化时,对过期键会如何处理 + +RDB 持久化 + +- **RDB 文件生成阶段** - 从内存状态持久化成 RDB(文件)的时候,会对 key 进行过期检查,**过期的键“不会”被保存到新的 RDB 文件中**,因此 Redis 中的过期键不会对生成新 RDB 文件产生任何影响。 +- **RDB 加载阶段** - RDB 加载阶段时,要看服务器是主服务器还是从服务器,分别对应以下两种情况: + - **如果 Redis 是“主服务器”运行模式的话,在载入 RDB 文件时,程序会对文件中保存的键进行检查,过期键“不会”被载入到数据库中**。所以过期键不会对载入 RDB 文件的主服务器造成影响; + - **如果 Redis 是“从服务器”运行模式的话,在载入 RDB 文件时,不论键是否过期都会被载入到数据库中**。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。所以一般来说,过期键对载入 RDB 文件的从服务器也不会造成影响。 + +AOF 持久化 + +- **AOF 文件写入阶段** - 当 Redis 以 AOF 模式持久化时,**如果数据库某个过期键还没被删除,那么 AOF 文件会保留此过期键,当此过期键被删除后,Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值**。 +- **AOF 重写阶段** - 执行 AOF 重写时,会对 Redis 中的键值对进行检查,**已过期的键不会被保存到重写后的 AOF 文件中**,因此不会对 AOF 重写造成任何影响。 + +### 主从复制时,对过期键会如何处理 + +当 Redis 运行在主从模式下时,**从库不会进行过期扫描,从库对过期的处理是被动的**。也就是即使从库中的 key 过期了,如果有客户端访问从库时,依然可以得到 key 对应的值,像未过期的键值对一样返回。 + +从库的过期键处理依靠主服务器控制,**主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库**,从库通过执行这条 del 指令来删除过期的 key。 + +### Redis 内存淘汰策略 + +【问题】 + +- Redis 内存不足时,怎么办? +- Redis 有哪些内存淘汰策略? +- 如何选择内存淘汰策略? + +【解答】 + +(1)Redis 内存淘汰要点 + +- **失效时间** - 作为一种定期清理无效数据的重要机制,在 Redis 提供的诸多命令中,`EXPIRE`、`EXPIREAT`、`PEXPIRE`、`PEXPIREAT` 以及 `SETEX` 和 `PSETEX` 均可以用来设置一条键值对的失效时间。而一条键值对一旦被关联了失效时间就会在到期后自动删除(或者说变得无法访问更为准确)。 +- **最大缓存** - Redis 允许通过 `maxmemory` 参数来设置内存最大值。当内存达设定的阀值,就会触发**内存淘汰**。 +- **内存淘汰** - 内存淘汰是为了更好的利用内存——清理部分缓存,以此换取内存的利用率,即尽量保证 Redis 缓存中存储的是热点数据。 + +(2)Redis 内存淘汰策略 + +- **不淘汰** + + - **`noeviction`** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。这是 Redis 默认的策略。 + +- **在过期键中进行淘汰** + + - **`volatile-random`** - 在设置了过期时间的键空间中,随机移除某个 key。 + + - **`volatile-ttl`** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。 + + - **`volatile-lru`** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。 + + - **`volatile-lfu`** (Redis 4.0 新增)- 淘汰所有设置了过期时间的键值中,最少使用的键值。 + +- **在所有键中进行淘汰** + - **`allkeys-lru`** - 在主键空间中,优先移除最近未使用的 key。 + - **`allkeys-random`** - 在主键空间中,随机移除某个 key。 + - **`allkeys-lfu`** (Redis 4.0 新增) - 淘汰整个键值中最少使用的键值。 + +(3)如何选择内存淘汰策略 + +- 如果数据呈现幂等分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 `allkeys-lru` 或 `allkeys-lfu`。 +- 如果数据呈现平均分布,也就是所有的数据访问频率都相同,则使用 `allkeys-random`。 +- 若 Redis 既用于缓存,也用于持久化存储时,适用 `volatile-lru` 、`volatile-lfu`、`volatile-random`。但是,这种情况下,也可以部署两个 Redis 集群来达到同样目的。 +- 为 key 设置过期时间实际上会消耗更多的内存。因此,如果条件允许,建议使用 `allkeys-lru` 或 `allkeys-lfu`,从而更高效的使用内存。 + +## Redis 持久化 + +### Redis 如何保证数据不丢失 + +【问题】 + +- Redis 如何保证数据不丢失? +- Redis 有几种持久化方式? + +【解答】 + +为了追求性能,Redis 的读写都是在内存中完成的。一旦重启,内存中的数据就会清空,为了保证数据不丢失,Redis 支持持久化机制。 + +Redis 有三种持久化方式 + +- RDB 快照 +- AOF 日志 +- 混合持久化 + +### AOF 的实现原理 + +【问题】 + +- AOF 的实现原理是什么? +- 为什么先执行命令,再把数据写入日志呢? + +【解答】 + +**Redis 命令请求会先保存到 AOF 缓冲区,再定期写入并同步到 AOF 文件**。 + +AOF 的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。 + +- **命令追加** - 当 Redis 服务器开启 AOF 功能时,服务器在执行完一个写命令后,会以 Redis 命令协议格式将被执行的写命令追加到 AOF 缓冲区的末尾。 +- **文件写入**和**文件同步** + - Redis 的服务器进程就是一个事件循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复。而时间事件则负责执行想 `serverCron` 这样的定时运行的函数。 + - 因为服务器在处理文件事件时可能会执行写命令,这些写命令会被追加到 AOF 缓冲区,服务器每次结束事件循环前,都会根据 `appendfsync` 选项来判断 AOF 缓冲区内容是否需要写入和同步到 AOF 文件中。 + +先执行命令,再把数据写入 AOF 日志有两个好处: + +- **避免额外的检查开销** +- **不会阻塞当前写操作命令的执行** + +当然,这样做也会有弊端: + +- **数据可能会丢失:** +- **可能阻塞其他操作:** + +### AOF 的回写策略有几种 + +**Redis 命令请求会先保存到 AOF 缓冲区,再定期写入并同步到 AOF 文件**。 + +`appendfsync` 不同选项决定了不同的持久化行为: + +- **`always`** - 将 AOF 缓冲区中所有内容写入并同步到 AOF 文件。这种方式是最数据最安全的,但也是性能最差的。 +- **`no`** - 将 AOF 缓冲区所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统决定。这种方式是数据最不安全的,一旦出现故障,未来得及同步的所有数据都会丢失。 +- **`everysec`** - `appendfsync` 默认选项。将 AOF 缓冲区所有内容写入到 AOF 文件,如果上次同步 AOF 文件的时间距离现在超过一秒钟,那么再次对 AOF 文件进行同步,这个同步操作是有一个线程专门负责执行的。这张方式是前面两种的这种方案——性能足够好,且即使出现故障,仅丢失一秒钟内的数据。 + +`appendfsync` 选项的不同值对 AOF 持久化功能的安全性、以及 Redis 服务器的性能有很大的影响。 + +### AOF 重写机制 + +【问题】 + +- AOF 日志过大时,怎么办? +- AOF 重写流程是怎样的? +- AOF 重写时,可以处理请求吗? + +【解答】 + +当 AOF 日志过大时,恢复过程就会很久。为了避免此问题,Redis 提供了 AOF 重写机制,即 AOF 日志大小超过所设阈值后,启动 AOF 重写,压缩 AOF 文件。 + +AOF 重写机制是,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到新的 AOF 日志中,等到全部记录完成后,就使用新的 AOF 日志替换现有的 AOF 日志。 + +作为一种辅助性功能,显然 Redis 并不想在 AOF 重写时阻塞 Redis 服务接收其他命令。因此,Redis 决定通过 `BGREWRITEAOF` 命令创建一个子进程,然后由子进程负责对 AOF 文件进行重写,这与 `BGSAVE` 原理类似。 + +- 在执行 `BGREWRITEAOF` 命令时,Redis 服务器会维护一个 AOF 重写缓冲区。当 AOF 重写子进程开始工作后,Redis 每执行完一个写命令,会同时将这个命令发送给 AOF 缓冲区和 AOF 重写缓冲区。 +- 由于彼此不是在同一个进程中工作,AOF 重写不影响 AOF 写入和同步。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。 +- 最后,服务器用新的 AOF 文件替换就的 AOF 文件,以此来完成 AOF 重写操作。 + +![BGREWRITEAOF 流程](https://raw.githubusercontent.com/dunwu/images/master/snap/202309171957918.png) + +### RDB 的实现原理 + +【问题】 + +- RDB 的实现原理是什么? +- 生成 RDB 快照时,Redis 可以响应请求吗? + +【解答】 + +[**`BGSAVE`**](https://redis.io/commands/bgsave) 命令会**“派生”**(fork)一个子进程,由子进程负责创建 RDB 文件,服务器进程继续处理命令请求,所以**该命令“不会阻塞”服务器**。 + +![BGSAVE 流程](https://raw.githubusercontent.com/dunwu/images/master/snap/202309172009198.png) + +### 为什么会有混合持久化? + +RDB 优点是数据恢复速度快,但是快照的频率不好把握。频率太低,丢失的数据就会比较多,频率太高,就会影响性能。 + +AOF 优点是丢失数据少,但是数据恢复不快。 + +为了集成了两者的优点, Redis 4.0 提出了**混合使用 AOF 日志和内存快照**,也叫混合持久化,既保证了 Redis 重启速度,又降低数据丢失风险。 + +混合持久化工作在 **AOF 日志重写过程**,当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。 + +也就是说,使用了混合持久化,AOF 文件的**前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据**。 + +这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样**加载的时候速度会很快**。 + +加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得**数据更少的丢失**。 + +**混合持久化优点:** + +- 混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。 + +**混合持久化缺点:** + +- AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差; +- 兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。 + +## Redis 高可用 + +【问题】 + +Redis 如何保证高可用? + +### Redis 主从复制 + +【问题】 + +- Redis 复制的工作原理?Redis 旧版复制和新版复制有何不同? +- Redis 主从节点间如何复制数据? +- Redis 的数据一致性是强一致性吗? + +【解答】 + +(1)旧版复制基于 `SYNC` 命令实现。分为同步(sync)和命令传播(command propagate)两个操作。这种方式存在缺陷:不能高效处理断线重连后的复制情况。 + +(2)新版复制基于 `PSYNC` 命令实现。同步操作分为了两块: + +- **`完整重同步(full resychronization)`** 用于初次复制; +- **`部分重同步(partial resychronization)`** 用于断线后重复制。 + - 主从服务器的**复制偏移量(replication offset)** + - 主服务器的**复制积压缓冲区(replication backlog)** + - **服务器的运行 ID** + +(3)Redis 集群主从节点复制的工作流程: + +- 步骤 1. 设置主从服务器 +- 步骤 2. 主从服务器建立 TCP 连接。 +- 步骤 3. 发送 PING 检查通信状态。 +- 步骤 4. 身份验证。 +- 步骤 5. 发送端口信息。 +- 步骤 6. 同步。 +- 步骤 7. 命令传播。 + +(4)由于主从复制是**异步**的,具体来说,在主从服务器命令传播阶段,主服务器收到新的写命令后,会发送给从服务器。但是,主服务器并不会等到从服务器实际执行完命令后,再把结果返回给客户端,而是主服务器自己在本地执行完命令后,就会向客户端返回结果了。如果从服务器还没有执行主服务器同步过来的命令,主从服务器间的数据就不一致了。所以,无法实现强一致性保证(主从数据时时刻刻保持一致),数据不一致是难以避免的。 + +### Redis 哨兵 + +【问题】 + +- Redis 哨兵的功能? +- Redis 哨兵的原理? +- Redis 哨兵如何选举 Leader? +- Redis 如何实现故障转移? + +【解答】 + +(1)Redis 主从复制模式无法自动故障转移,也就是说,一旦主服务器宕机,需要手动恢复。为了解决此问题,Redis 增加了哨兵模式(Sentinel)。 + +(2)由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。 + +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131135847.png) + +### Redis 集群 + +当 Redis 数据量超出单机的极限,就需要通过分区技术来分而治之。 + +Redis 采用的分区策略是:使用虚拟哈希槽来映射节点和数据。在 Redis Cluster 中,为整个集群分配 16384 个哈希槽。每个节点都会被分配一定的哈希槽,这个过程可以是自动分配,也可以是手动分配。任何一个槽没有被分配,那么集群处于下线状态。 + +当客户端向服务端发起读写请求时,先要根据 key 计算其所属的哈希槽(计算公式:CRC16(KEY) mod 16384),然后获取该哈希槽所属的节点。这样,就完成了寻址过程。 + +## Redis 脑裂 + +### 什么是脑裂 + +分布式系统的脑裂问题(Split-Brain Problem)是一个严重的一致性问题,通常发生在分布式系统中的节点之间失去通信或部分通信时。这个问题的名称源自脑裂的比喻,就像一个分布式系统被分成多个部分的"脑",每个部分独立运行,而没有协调一致的方式。 + +脑裂问题通常发生在以下情况下: + +1. **网络分区**:当分布式系统中的网络发生问题,导致节点之间无法互相通信或只能部分通信时。这可能是由于网络故障、硬件故障、防火墙配置问题等原因引起的。 +2. **节点故障**:当分布式系统的某个节点崩溃或出现故障,但其他节点无法确定该节点的状态,可能导致脑裂问题。 + +脑裂问题的典型情况是,在网络分区或节点故障后,分布式系统的一部分节点认为另一部分节点已经不可用,因此开始采取某种措施,比如选举新的领袖或切换到备份模式。然而,在某些情况下,网络分区可能会解除,或者节点故障可能会自行修复,导致系统中存在多个独立运行的子系统,每个子系统都认为自己是正确的。 + +这种情况下,脑裂问题可能导致以下问题: + +1. **数据不一致性**:不同子系统可能具有不同的数据状态,这可能会导致数据不一致性和冲突。 +2. **资源冲突**:如果不同的子系统尝试访问相同的资源,可能会发生资源冲突和竞争条件。 +3. **性能问题**:系统中的资源可能被多次分配,从而浪费了资源并降低了性能。 + +为了解决脑裂问题,分布式系统通常需要采用一些机制,如投票算法、选举协议、心跳检测等,以确保在出现网络分区或节点故障时,系统能够正确地识别和处理问题,并维护一致性。这些机制可以帮助系统中的节点协同工作,避免脑裂问题的发生。然而,脑裂问题是分布式系统设计和管理中的复杂挑战之一,需要细致的规划和测试来确保系统的可靠性和稳定性。 + +### Redis 中的脑裂问题是如何产生的 + +在 Redis 主从架构中,部署方式一般是“一主多从”,主节点提供写操作,从节点提供读操作。 如果主节点的网络突然发生了问题,它与所有的从节点都失联了,但是此时的主节点和客户端的网络是正常的,这个客户端并不知道 Redis 内部已经出现了问题,还在照样的向这个失联的主节点写数据(过程 A),此时这些数据被旧主节点缓存到了缓冲区里,因为主从节点之间的网络问题,这些数据都是无法同步给从节点的。 + +这时,哨兵也发现主节点失联了,它就认为主节点挂了(但实际上主节点正常运行,只是网络出问题了),于是哨兵就会在“从节点”中选举出一个 leader 作为主节点,这时集群就有两个主节点了 —— **脑裂出现了**。 + +然后,网络突然好了,哨兵因为之前已经选举出一个新主节点了,它就会把旧主节点降级为从节点(A),然后从节点(A)会向新主节点请求数据同步,**因为第一次同步是全量同步的方式,此时的从节点(A)会清空掉自己本地的数据,然后再做全量同步。所以,之前客户端在过程 A 写入的数据就会丢失了,也就是集群产生脑裂数据丢失的问题**。 + +总结一句话就是:由于网络问题,集群节点之间失去联系。主从数据不同步;重新平衡选举,产生两个主服务。等网络恢复,旧主节点会降级为从节点,再与新主节点进行同步复制的时候,由于从节点会清空自己的缓冲区,所以导致之前客户端写入的数据丢失了。 + +### 如何解决 Redis 中的脑裂问题 + +当主节点发现从节点下线或者通信超时的总数量小于阈值时,那么禁止主节点进行写数据,直接把错误返回给客户端。 + +在 Redis 的配置文件中有两个参数我们可以设置: + +- `min-slaves-to-write x`,主节点必须要有至少 x 个从节点连接,如果小于这个数,主节点会禁止写数据。 +- `min-slaves-max-lag x`,主从数据复制和同步的延迟不能超过 x 秒,如果超过,主节点会禁止写数据。 + +我们可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。 + +这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的写请求了。 + +即使原主库是假故障,它在假故障期间也无法响应哨兵心跳,也不能和从库进行同步,自然也就无法和从库进行 ACK 确认了。这样一来,min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足,**原主库就会被限制接收客户端写请求,客户端也就不能在原主库中写入新数据了**。 + +**等到新主库上线时,就只有新主库能接收和处理客户端请求,此时,新写的数据会被直接写到新主库中。而原主库会被哨兵降为从库,即使它的数据被清空了,也不会有新数据丢失。** + +再来举个例子。 + +假设我们将 min-slaves-to-write 设置为 1,把 min-slaves-max-lag 设置为 12s,把哨兵的 down-after-milliseconds 设置为 10s,主库因为某些原因卡住了 15s,导致哨兵判断主库客观下线,开始进行主从切换。 + +同时,因为原主库卡住了 15s,没有一个从库能和原主库在 12s 内进行数据复制,原主库也无法接收客户端请求了。 + +这样一来,主从切换完成后,也只有新主库能接收请求,不会发生脑裂,也就不会发生数据丢失的问题了。 + +## Redis 线程模型 + +### Redis 真的只有单线程吗? + +Redis 并非真的只有单线程。 + +- Redis 的主要工作包括接收客户端请求、解析请求和进行数据读写等操作,是由单线程来执行的,这也是常说 Redis 是单线程程序的原因。 +- Redis 还启动了 3 个线程来执行**文件关闭**、**AOF 同步写**和**惰性删除**等操作。 + +### Redis 单线程模式是怎样的? + +Redis 单线程模式指的是其核心网络模型为单线程模式。这个模式为 IO 多路复用+单线程读写请求,其中,IO 多路复用使得 Redis 可以同时处理多个客户端连接。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309241133046.png) + +### Redis 采用单线程为什么还这么快? + +> 参考:[Redis 为什么快](#Redis 为什么快) + +### Redis 6.0 之后为什么引入了多线程? + +随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 IO 的处理上,也就是说,**单个主线程处理网络请求的速度跟不上底层网络硬件的速度。** + +为了提高网络 I/O 的并行度,Redis 6.0 对于网络 I/O 采用多线程来处理。但是,对于命令的执行,Redis 仍然使用单线程来处理。 + +Redis 官方表示,**Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202309241148273.png) + +## Redis 事务 + +【问题】 + +- Redis 的并发竞争问题是什么?如何解决这个问题? +- Redis 支持事务吗? +- Redis 事务是严格意义的事务吗?Redis 为什么不支持回滚。 +- Redis 事务如何工作? +- 了解 Redis 事务中的 CAS 行为吗? + +【解答】 + +**Redis 提供的不是严格的事务,Redis 只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去**。 + +Redis 不支持回滚的理由: + +- Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面。 +- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 + +`MULTI` 、 `EXEC` 、 `DISCARD` 和 `WATCH` 是 Redis 事务相关的命令。 + +Redis 有天然解决这个并发竞争问题的类 CAS 乐观锁方案:每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。 + +## Redis 管道 + +【问题】 + +- 除了事务,还有其他批量执行 Redis 命令的方式吗? + +【解答】 + +Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。Redis 支持管道技术。管道技术允许请求以异步方式发送,即旧请求的应答还未返回的情况下,允许发送新请求。这种方式可以大大提高传输效率。使用管道发送命令时,Redis Server 会将部分请求放到缓存队列中(占用内存),执行完毕后一次性发送结果。如果需要发送大量的命令,会占用大量的内存,因此应该按照合理数量分批次的处理。 + +## Redis 应用 + +### 缓存设计 + +【问题】 + +如何避免缓存雪崩、缓存击穿、缓存穿透? + +有哪些更新缓存策略?采用那种策略比较好? + +如何保证缓存一致性? + +有哪些常见的内存淘汰算法 + +LRU 算法的原理是什么 + +LFU 算法的原理是什么 + +### 分布式锁 + +### 大 Key 处理 + +#### 什么是大 Key + +大 key 并不是指 key 的值很大,而是 key 对应的 value 很大。 + +一般而言,下面这两种情况被称为大 key: + +- String 类型的值大于 10 KB; +- Hash、List、Set、ZSet 类型的元素的个数超过 5000 个; + +#### 大 Key 的影响 + +大 key 会带来以下四种影响: + +- **客户端超时阻塞**。由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。 +- **引发网络阻塞**。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。 +- **阻塞工作线程**。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。 +- **内存分布不均**。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。 + +#### 如何找到大 Key + +**_1、redis-cli --bigkeys 查找大 key_** + +可以通过 redis-cli --bigkeys 命令查找大 key: + +```shell +redis-cli -h 127.0.0.1 -p6379 -a "password" -- bigkeys +``` + +使用的时候注意事项: + +- 最好选择在从节点上执行该命令。因为主节点上执行时,会阻塞主节点; +- 如果没有从节点,那么可以选择在 Redis 实例业务压力的低峰阶段进行扫描查询,以免影响到实例的正常运行;或者可以使用 -i 参数控制扫描间隔,避免长时间扫描降低 Redis 实例的性能。 + +该方式的不足之处: + +- 这个方法只能返回每种类型中最大的那个 bigkey,无法得到大小排在前 N 位的 bigkey; +- 对于集合类型来说,这个方法只统计集合元素个数的多少,而不是实际占用的内存量。但是,一个集合中的元素个数多,并不一定占用的内存就多。因为,有可能每个元素占用的内存很小,这样的话,即使元素个数有很多,总内存开销也不大; + +**_2、使用 SCAN 命令查找大 key_** + +使用 SCAN 命令对数据库扫描,然后用 TYPE 命令获取返回的每一个 key 的类型。 + +对于 String 类型,可以直接使用 STRLEN 命令获取字符串的长度,也就是占用的内存空间字节数。 + +对于集合类型来说,有两种方法可以获得它占用的内存大小: + +- 如果能够预先从业务层知道集合元素的平均大小,那么,可以使用下面的命令获取集合元素的个数,然后乘以集合元素的平均大小,这样就能获得集合占用的内存大小了。List 类型:`LLEN` 命令;Hash 类型:`HLEN` 命令;Set 类型:`SCARD` 命令;Sorted Set 类型:`ZCARD` 命令; +- 如果不能提前知道写入集合的元素大小,可以使用 `MEMORY USAGE` 命令(需要 Redis 4.0 及以上版本),查询一个键值对占用的内存空间。 + +**_3、使用 RdbTools 工具查找大 key_** + +使用 RdbTools 第三方开源工具,可以用来解析 Redis 快照(RDB)文件,找到其中的大 key。 + +比如,下面这条命令,将大于 10 kb 的 key 输出到一个表格文件。 + +```shell +rdb dump.rdb -c memory --bytes 10240 -f redis.csv +``` + +#### 如何删除大 Key + +如果大 Key 过大,删除时间过长,会阻塞 Redis 主线程,导致主线程无法及时响应其他请求。因此,删除大 Key 时需要考虑分批、异步处理。 + +**_1、分批次删除_** + +对于**删除大 Hash**,使用 `hscan` 命令,每次获取 100 个字段,再用 `hdel` 命令,每次删除 1 个字段。 + +Python 代码: + +```python +def del_large_hash(): + r = redis.StrictRedis(host='redis-host1', port=6379) + large_hash_key ="xxx" #要删除的大hash键名 + cursor = '0' + while cursor != 0: + # 使用 hscan 命令,每次获取 100 个字段 + cursor, data = r.hscan(large_hash_key, cursor=cursor, count=100) + for item in data.items(): + # 再用 hdel 命令,每次删除1个字段 + r.hdel(large_hash_key, item[0]) +``` + +对于**删除大 List**,通过 `ltrim` 命令,每次删除少量元素。 + +Python 代码: + +```python +def del_large_list(): + r = redis.StrictRedis(host='redis-host1', port=6379) + large_list_key = 'xxx' #要删除的大list的键名 + while r.llen(large_list_key)>0: + #每次只删除最右100个元素 + r.ltrim(large_list_key, 0, -101) +``` + +对于**删除大 Set**,使用 `sscan` 命令,每次扫描集合中 100 个元素,再用 `srem` 命令每次删除一个键。 + +Python 代码: + +```python +def del_large_set(): + r = redis.StrictRedis(host='redis-host1', port=6379) + large_set_key = 'xxx' # 要删除的大set的键名 + cursor = '0' + while cursor != 0: + # 使用 sscan 命令,每次扫描集合中 100 个元素 + cursor, data = r.sscan(large_set_key, cursor=cursor, count=100) + for item in data: + # 再用 srem 命令每次删除一个键 + r.srem(large_size_key, item) +``` + +对于**删除大 ZSet**,使用 `zremrangebyrank` 命令,每次删除 top 100 个元素。 + +Python 代码: + +```python +def del_large_sortedset(): + r = redis.StrictRedis(host='large_sortedset_key', port=6379) + large_sortedset_key='xxx' + while r.zcard(large_sortedset_key)>0: + # 使用 zremrangebyrank 命令,每次删除 top 100个元素 + r.zremrangebyrank(large_sortedset_key,0,99) +``` + +**_2、异步删除_** + +从 Redis 4.0 版本开始,可以采用**异步删除**法,**用 unlink 命令代替 del 来删除**。 + +这样 Redis 会将这个 key 放入到一个异步线程中进行删除,这样不会阻塞主线程。 + +除了主动调用 unlink 命令实现异步删除之外,我们还可以通过配置参数,达到某些条件的时候自动进行异步删除。 + +主要有 4 种场景,默认都是关闭的: + +```text +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del +noslave-lazy-flush no +``` + +它们代表的含义如下: + +- lazyfree-lazy-eviction:表示当 Redis 运行内存超过 maxmeory 时,是否开启 lazy free 机制删除; +- lazyfree-lazy-expire:表示设置了过期时间的键值,当过期之后是否开启 lazy free 机制删除; +- lazyfree-lazy-server-del:有些指令在处理已存在的键时,会带有一个隐式的 del 键的操作,比如 rename 命令,当目标键已存在,Redis 会先删除目标键,如果这些目标键是一个 big key,就会造成阻塞删除的问题,此配置表示在这种场景中是否开启 lazy free 机制删除; +- slave-lazy-flush:针对 slave (从节点) 进行全量数据同步,slave 在加载 master 的 RDB 文件前,会运行 flushall 来清理自己的数据,它表示此时是否开启 lazy free 机制删除。 + +建议开启其中的 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del 等配置,这样就可以有效的提高主线程的执行效率。 + +## 参考资料 + +- [面试中关于 Redis 的问题看这篇就够了](https://juejin.im/post/5ad6e4066fb9a028d82c4b66) +- [advanced-java](https://github.com/doocs/advanced-java#缓存) +- [Redis 常见面试题](https://xiaolincoding.com/redis/base/redis_interview.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" index 5cb711e80d..286bc36c2c 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/README.md" @@ -1,4 +1,5 @@ --- +icon: logos:redis title: Redis 教程 date: 2020-02-10 14:27:39 categories: @@ -9,76 +10,103 @@ tags: - 数据库 - KV数据库 - Redis -permalink: /pages/fe3808/ +permalink: /pages/83e307/ hidden: true +index: false --- # Redis 教程 -> Redis 最典型的应用场景是作为分布式缓存。 -> -> 学习 Redis,有必要深入理解缓存的原理,以及 Redis 作为一种缓存方案,在系统应用中的定位。 -> -> 参考:[缓存基本原理](https://dunwu.github.io/design/distributed/分布式缓存.html),有助于理解缓存的特性、原理,使用缓存常见的问题及解决方案。 +**Redis 是一种内存数据库**,对数据的读写操作都是在内存中完成。因此其**读写速度非常快**,常用于**缓存,消息队列、分布式锁等场景**。 + +- **高性能** – Redis 的数据读写都是在内存中完成,因此性能极高。 +- **高并发** - Redis 的读速度约为 10w+ QPS,写的速度约为 8w+ TPS,将近是 Mysql 的 10 倍。 + +**Redis 支持多种数据类型**,如:String(字符串)、Hash(哈希)、 List (列表)、Set(集合)、Zset(有序集合)、Bitmaps(位图)、HyperLogLog(基数统计)、GEO(地理空间)、Stream(流)。Redis 对数据类型的操作都是**原子性**的,因为执行命令由单线程负责的,不存在并发竞争的问题。 + +**Redis 的读写采用单线程模型**,因此,其操作天然就具有**原子性**。 + +Redis 支持两种持久化策略:RDB 和 AOF。 + +Redis 支持过期删除和内存淘汰,因此常被用于作为缓存。 + +Redis 有多种高可用方案:**主从复制**模式、**哨兵**模式、**集群**模式。 + +Redis 支持很多丰富的特性,如:**事务** 、**Lua 脚本**、**发布订阅**等等。 + +![](https://architecturenotes.co/content/images/size/w2400/2022/08/Redis-v2-01-1.jpg) ## 📖 内容 -### [Redis 面试总结 💯](01.Redis面试总结.md) +### [Redis 基本数据类型](01.Redis基本数据类型.md) + +> 关键词:`String`、`Hash`、`List`、`Set`、`Zset` -### [Redis 应用指南 ⚡](02.Redis应用指南.md) +### [Redis 高级数据类型](02.Redis高级数据类型.md) -> 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` +> 关键词:`BitMap`、`HyperLogLog`、`Geo`、`Stream` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) +### [Redis 数据结构](03.Redis数据结构.md) -### [Redis 数据类型和应用](03.Redis数据类型和应用.md) +> 关键词:`对象`、`SDS`、`链表`、`字典`、`跳表`、`整数集合`、`压缩列表` -> 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` +### [Redis 过期删除和内存淘汰](11.Redis过期删除和内存淘汰.md) -![Redis 数据类型](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200226113813.png) +> 关键词:`定时删除`、`惰性删除`、`定期删除`、`LRU`、`LFU` -### [Redis 持久化](04.Redis持久化.md) +### [Redis 持久化](12.Redis持久化.md) > 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224214047.png) +### [Redis 事件](13.Redis事件.md) -### [Redis 复制](05.Redis复制.md) +> 关键词:`文件事件`、`时间事件` -> 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` +### [Redis 复制](21.Redis复制.md) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200712182603.png) +> 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`命令传播`、`心跳` -### [Redis 哨兵](06.Redis哨兵.md) +### [Redis 哨兵](22.Redis哨兵.md) -> Redis 哨兵(Sentinel)是 Redis 的高可用性(Hight Availability)解决方案。 -> -> Redis 哨兵是 Raft 算法 的具体实现。 -> -> 关键词:`Sentinel`、`PING`、`INFO`、`Raft` +> 关键词:`高可用`、`监控`、`选主`、`故障转移`、`Raft` -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713072747.png) +### [Redis 集群](23.Redis集群.md) -### [Redis 集群](07.Redis集群.md) +> 关键词:`高可用`、`监控`、`选主`、`故障转移`、`分区`、`Raft`、`Gossip` -> 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` +### [Redis 发布订阅](31.Redis发布订阅.md) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713100613.png) +> 关键词:`订阅`、`SUBSCRIBE`、`PSUBSCRIBE`、`PUBLISH`、`观察者模式` -### [Redis 实战](08.Redis实战.md) +### [Redis 独立功能](32.Redis事务.md) -> 关键词:`缓存`、`分布式锁`、`布隆过滤器` +> 关键词:`事务`、`ACID`、`MULTI`、`EXEC`、`DISCARD`、`WATCH` + +### [Redis 管道](33.Redis管道.md) + +> 关键词:`Pipeline` + +### [Redis 脚本](34.Redis脚本.md) + +> 关键词:`Lua` -### [Redis 运维 🔨](20.Redis运维.md) +### [Redis 运维](41.Redis运维.md) + +> 关键词:`安装`、`配置`、`命令`、`集群`、`客户端` + +### [Redis 实战](42.Redis实战.md) + +> 关键词:`缓存`、`分布式锁`、`布隆过滤器` -> 关键词:`安装`、`命令`、`集群`、`客户端` +### [Redis 面试](99.Redis面试.md) ## 📚 资料 - **官网** - [Redis 官网](https://redis.io/) - - [Redis github](https://github.com/antirez/redis) + - [Redis Github](https://github.com/antirez/redis) - [Redis 官方文档中文版](http://redis.cn/) + - [Redis 在线环境](https://try.redis.io/) - **书籍** - [《Redis 实战》](https://item.jd.com/11791607.html) - [《Redis 设计与实现》](https://item.jd.com/11486101.html) @@ -92,12 +120,16 @@ hidden: true - **资源汇总** - [awesome-redis](https://github.com/JamzyWang/awesome-redis) - **Redis Client** + - [Jedis](https://github.com/xetorthio/jedis) - 最流行的 Redis Java 客户端 + - [Redisson](https://github.com/redisson/redisson) - 额外提供了很多的分布式服务特性,如:分布式锁、分布式 Java 常用对象(BitSet、BlockingQueue、CountDownLatch 等) + - [Lettuce](https://github.com/lettuce-io/lettuce-core) - Spring Boot 2.0 默认 Redis 客户端 - [spring-data-redis 官方文档](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/) - - [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) - - [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) + - [Redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) + - [Redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) + - [RedisDesktopManager](https://github.com/uglide/RedisDesktopManager) ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/redis-cheat-sheets.pdf" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/redis-cheat-sheets.pdf" deleted file mode 100644 index 25b679374a..0000000000 Binary files "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/01.Redis/redis-cheat-sheets.pdf" and /dev/null differ diff --git "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/03.Memcached.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/02.Memcached.md" similarity index 93% rename from "source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/03.Memcached.md" rename to "source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/02.Memcached.md" index 0eb78f56a3..9f3cef8eba 100644 --- "a/source/_posts/01.Java/14.\344\270\255\351\227\264\344\273\266/02.\347\274\223\345\255\230/03.Memcached.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/02.Memcached.md" @@ -1,21 +1,21 @@ --- +icon: logos:memcached title: Memcached 快速入门 date: 2022-02-17 22:34:30 +order: 02 categories: - - Java - - 中间件 - - 缓存 + - 数据库 + - KV数据库 tags: - - Java - - 中间件 - - 缓存 - - 面试 -permalink: /pages/dbe77c/ + - 数据库 + - KV数据库 + - Memcached +permalink: /pages/56cf9a/ --- # Memcached 快速入门 -## 一、Memcached 简介 +## Memcached 简介 Memcached 是一个自由开源的,高性能,分布式内存对象缓存系统。 @@ -32,7 +32,7 @@ memcached 作为高速运行的分布式缓存服务器,具有以下的特点 - 内置内存存储方式 - memcached 不互相通信的分布式 -## 二、Memcached 命令 +## Memcached 命令 可以通过 telnet 命令并指定主机 ip 和端口来连接 Memcached 服务。 @@ -52,7 +52,7 @@ END 结束行 quit 退出 ``` -## 三、Java 连接 Memcached +## Java 连接 Memcached 使用 Java 程序连接 Memcached,需要在你的 classpath 中添加 Memcached jar 包。 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/README.md" new file mode 100644 index 0000000000..63646933ee --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/05.KV\346\225\260\346\215\256\345\272\223/README.md" @@ -0,0 +1,52 @@ +--- +title: KV 数据库 +date: 2023-09-08 15:52:57 +categories: + - 数据库 + - KV数据库 +tags: + - 数据库 + - KV数据库 +permalink: /pages/85202a/ +hidden: true +index: false +--- + +# KV 数据库 + +## 📖 内容 + + + +## 📚 资料 + +### Redis 资料 + +- **官网** + - [Redis 官网](https://redis.io/) + - [Redis Github](https://github.com/antirez/redis) + - [Redis 官方文档中文版](http://redis.cn/) + - [Redis 在线环境](https://try.redis.io/) +- **书籍** + - [《Redis 实战》](https://item.jd.com/11791607.html) + - [《Redis 设计与实现》](https://item.jd.com/11486101.html) +- **教程** + - [Redis 命令参考](http://redisdoc.com/) +- **文章** + - [Introduction to Redis](https://www.slideshare.net/dvirsky/introduction-to-redis) + - [《我们一起进大厂》系列- Redis 基础](https://juejin.im/post/5db66ed9e51d452a2f15d833) +- **源码** + - [《Redis 实战》配套 Python 源码](https://github.com/josiahcarlson/redis-in-action) +- **资源汇总** + - [awesome-redis](https://github.com/JamzyWang/awesome-redis) +- **Redis Client** + - [spring-data-redis 官方文档](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/) + - [Redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) + - [Redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content) + - [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002) + - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) + - [RedisDesktopManager](https://github.com/uglide/RedisDesktopManager) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" index 4f0db0813e..f7a3c53c81 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/01.HBase\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: HBase 快速入门 date: 2020-02-10 14:27:39 +order: 01 categories: - 数据库 - 列式数据库 @@ -37,7 +38,7 @@ Hadoop 的缺陷在于:它只能执行批处理,并且只能以顺序方式 HBase 是一种类似于 `Google’s Big Table` 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601170449.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601170449.png) HBase 的**核心特性**如下: @@ -102,7 +103,7 @@ HBase 的典型应用场景 - Cell:单元格是行、列族和列限定符的组合,包含一个值和一个时间戳,代表值的版本。 - Timestamp:时间戳写在每个值旁边,是给定版本值的标识符。 默认情况下,时间戳表示写入数据时 RegionServer 上的时间,但您可以在将数据放入单元格时指定不同的时间戳值。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164224778.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164224778.png) ### 特性比较 @@ -289,4 +290,4 @@ HBase 的典型应用场景 - [An In-Depth Look at the HBase Architecture](https://mapr.com/blog/in-depth-look-hbase-architecture) - **教程** - https://github.com/heibaiying/BigData-Notes - - https://www.cloudduggu.com/hbase/introduction/ + - https://www.cloudduggu.com/hbase/introduction/ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" index 1cf3ac0b54..ff19a04eb7 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/02.HBase\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -1,6 +1,7 @@ --- title: HBase 数据模型 date: 2023-03-16 15:58:10 +order: 02 categories: - 数据库 - 列式数据库 @@ -40,7 +41,7 @@ HBase 是一个面向 `列` 的数据库管理系统,这里更为确切的而 - - **`Timestamp`**:`Cell` 的版本通过时间戳来索引,时间戳的类型是 64 位整型,时间戳可以由 HBase 在数据写入时自动赋值,也可以由客户显式指定。每个 `Cell` 中,不同版本的数据按照时间戳倒序排列,即最新的数据排在最前面。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164224778.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164224778.png) ## HBase 物理存储结构 @@ -54,7 +55,7 @@ HBase 自动将表水平划分成区域(Region)。每个 Region 由表中 Ro - 该表具有两个列族,分别是 personal 和 office; - 其中列族 personal 拥有 name、city、phone 三个列,列族 office 拥有 tel、addres 两个列。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601172926.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601172926.png) > _图片引用自 : HBase 是列式存储数据库吗_ *https://www.iteblog.com/archives/2498.html* diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" index d4e29c79a3..7eb925c752 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/03.HBaseSchema\350\256\276\350\256\241.md" @@ -1,6 +1,7 @@ --- title: HBase Schema 设计 date: 2023-03-15 20:28:32 +order: 03 categories: - 数据库 - 列式数据库 @@ -226,4 +227,4 @@ bucket = timestamp % bucketNum ## 参考资料 -- [HBase 官方文档之 HBase and Schema Design](https://hbase.apache.org/book.html#schema) +- [HBase 官方文档之 HBase and Schema Design](https://hbase.apache.org/book.html#schema) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" index 18a82c5602..7cdfa997cf 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/04.HBase\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: HBase 架构 date: 2020-07-24 06:52:07 +order: 04 categories: - 数据库 - 列式数据库 @@ -21,7 +22,7 @@ permalink: /pages/62f8d9/ ### 概览 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200612151239.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200612151239.png) HBase 主要处理两种文件:预写日志(WAL)和实际数据文件 HFile。一个基本的流程是客户端首先联系 ZooKeeper 集群查找行键。上述过程是通过 ZooKeeper 获取欧含有 `-ROOT-` 的 region 服务器来完成的。通过含有 `-ROOT-` 的 region 服务器可以查询到含有 `.META.` 表中对应的 region 服务器名,其中包含请求的行键信息。这两种内容都会被缓存下来,并且只查询一次。最终,通过查询 .META. 服务器来获取客户端查询的行键数据所在 region 的服务器名。 @@ -29,13 +30,13 @@ HBase 主要处理两种文件:预写日志(WAL)和实际数据文件 HFil HBase Table 中的所有行按照 `Row Key` 的字典序排列。HBase Table 根据 Row Key 的范围分片,每个分片叫做 `Region`。一个 `Region` 包含了在 start key 和 end key 之间的所有行。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551165887616.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551165887616.png) **HBase 支持自动分区**:每个表初始只有一个 `Region`,随着数据不断增加,`Region` 会不断增大,当增大到一个阀值的时候,`Region` 就会分裂为两个新的 `Region`。当 Table 中的行不断增多,就会有越来越多的 `Region`。 `Region` 是 HBase 中**分布式存储和负载均衡的最小单元**。这意味着不同的 `Region` 可以分布在不同的 `Region Server` 上。但一个 `Region` 是不会拆分到多个 Server 上的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601181219.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601181219.png) ### Region Server @@ -54,13 +55,13 @@ HBase Table 中的所有行按照 `Row Key` 的字典序排列。HBase Table 根 - Flush 发生时,创建 HFile Writer,第一个空的 Data Block 出现,初始化后的 Data Block 中为 Header 部分预留了空间,Header 部分用来存放一个 Data Block 的元数据信息。 - 而后,位于 MemStore 中的 KeyValues 被一个个 append 到位于内存中的第一个 Data Block 中: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166602999.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166602999.png) Region Server 存取一个子表时,会创建一个 Region 对象,然后对表的每个列族创建一个 `Store` 实例,每个 `Store` 会有 0 个或多个 `StoreFile` 与之对应,每个 `StoreFile` 则对应一个 `HFile`,HFile 就是实际存储在 HDFS 上的文件。 ## HBase 系统架构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551164744748.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551164744748.png) 和 HDFS、YARN 一样,**HBase 也遵循 master / slave 架构**: @@ -80,14 +81,14 @@ Region Server 存取一个子表时,会创建一个 Region 对象,然后对 - GFS 上的垃圾文件回收; - 处理 Schema 的更新请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166513572.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166513572.png) ### Region Server - Region Server 负责维护 Master Server 分配给它的 Region,并处理发送到 Region 上的 IO 请求; - 当 Region 过大,Region Server 负责自动分区,并通知 Master Server 记录更新。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200612151602.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200612151602.png) ### ZooKeeper @@ -100,7 +101,7 @@ ZooKeeper 的作用: - 实时监控 Region Server 的状态,将 Region Server 的上线和下线信息实时通知给 Master; - 存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family 等信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hbase/1551166447147.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hbase/1551166447147.png) 以上,最重要的一点是 ZooKeeper 如何保证 HBase 集群中只有一个 Master Server 的呢? @@ -136,7 +137,7 @@ HBase 内部保留名为 hbase:meta 的特殊目录表(catalog table)。它 注:`META` 表是 HBase 中一张特殊的表,它保存了所有 Region 的位置信息,META 表自己的位置信息则存储在 ZooKeeper 上。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601182655.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601182655.png) > 更为详细读取数据流程参考: > diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" index 95d54cc13a..5b69646d5a 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/10.HBaseJavaApi\345\237\272\347\241\200\347\211\271\346\200\247.md" @@ -1,6 +1,7 @@ --- title: HBase Java API 基础特性 date: 2023-03-15 20:28:32 +order: 10 categories: - 数据库 - 列式数据库 @@ -326,11 +327,11 @@ Connection 是一个集群连接,封装了与多台服务器(Matser/Region S - **HBase Master** :主要用于执行 HBaseAdmin 接口的一些操作,例如建表等; - **HBase RegionServer** :用于读、写数据。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230315202403.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230315202403.png) Connection 对象和实际的 Socket 连接之间的对应关系如下图: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230315202426.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230315202426.png) 在 HBase 客户端代码中,真正对应 Socket 连接的是 `RpcConnection` 对象。HBase 使用 `PoolMap` 这种数据结构来存储客户端到 HBase 服务器之间的连接。`PoolMap` 的内部有一个 `ConcurrentHashMap` 实例,其 key 是 `ConnectionId`(封装了服务器地址和用户 ticket),value 是一个 `RpcConnection` 对象的资源池。当 HBase 需要连接一个服务器时,首先会根据 `ConnectionId` 找到对应的连接池,然后从连接池中取出一个连接对象。 @@ -552,4 +553,4 @@ RPC 请求的次数=(行数\*每行列数)/Min(每行的列数,批量大小)/ ## 参考资料 - [《HBase 权威指南》](https://item.jd.com/11321037.html) -- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" index 7dbbceb0c2..0465b1dc81 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/11.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\350\277\207\346\273\244\345\231\250.md" @@ -1,6 +1,7 @@ --- title: HBase Java API 高级特性之过滤器 date: 2023-03-16 09:45:10 +order: 11 categories: - 数据库 - 列式数据库 @@ -379,4 +380,4 @@ scan.setFilter(filterList); ## 参考资料 - [《HBase 权威指南》](https://item.jd.com/11321037.html) -- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" index b6db0202bb..bd664f06df 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/12.HBaseJavaApi\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\345\215\217\345\244\204\347\220\206\345\231\250.md" @@ -1,6 +1,7 @@ --- title: HBase Java API 高级特性之协处理器 date: 2023-03-16 09:46:37 +order: 12 categories: - 数据库 - 列式数据库 @@ -21,4 +22,4 @@ permalink: /pages/5f1bc3/ ## 参考资料 - [《HBase 权威指南》](https://item.jd.com/11321037.html) -- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) +- [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" index ee69608c96..45626d1038 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/13.HBaseJavaApi\345\205\266\344\273\226\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -1,6 +1,7 @@ --- title: HBase Java API 其他高级特性 date: 2023-03-31 16:20:27 +order: 13 categories: - 数据库 - 列式数据库 @@ -154,4 +155,4 @@ try { - [《HBase 权威指南》](https://item.jd.com/11321037.html) - [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) -- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) +- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" index 3915ce8640..35968b59ec 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/14.HBaseJavaApi\347\256\241\347\220\206\345\212\237\350\203\275.md" @@ -1,6 +1,7 @@ --- title: HBase Java API 管理功能 date: 2023-04-13 16:36:48 +order: 14 categories: - 数据库 - 列式数据库 @@ -121,4 +122,4 @@ System.out.println("Table available: " + isOk); - [《HBase 权威指南》](https://item.jd.com/11321037.html) - [《HBase 权威指南》官方源码](https://github.com/larsgeorge/hbase-book) -- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) +- [连接 HBase 的正确姿势](https://developer.aliyun.com/article/581702) \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" index 3b9c76cb13..b63ad1fa0a 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/21.HBase\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: HBase 运维 date: 2019-05-07 20:19:25 +order: 21 categories: - 数据库 - 列式数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" index 209ef62027..9adaf918d8 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/22.HBase\345\221\275\344\273\244.md" @@ -1,6 +1,7 @@ --- title: HBase 命令 date: 2020-06-02 22:28:18 +order: 22 categories: - 数据库 - 列式数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" index 588cb320fb..e30929458d 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/01.HBase/README.md" @@ -10,6 +10,7 @@ tags: - HBase permalink: /pages/417be6/ hidden: true +index: false --- # HBase 教程 @@ -47,4 +48,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" index 7a18759d5c..dbab0361d2 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/02.Cassandra.md" @@ -1,6 +1,7 @@ --- title: Cassandra date: 2019-08-22 09:02:39 +order: 02 categories: - 数据库 - 列式数据库 @@ -54,6 +55,6 @@ Cassandra 的主要特点就是它不是一个数据库,而是由一堆数据 - [Cassandra 官网](http://cassandra.apache.org) - [Cassandra Github](https://github.com/apache/cassandra) -## :door: 传送门 +## 🚪 传送 -| [钝悟的博客](https://dunwu.github.io/blog/) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) | \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/README.md" new file mode 100644 index 0000000000..c4501ed299 --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/06.\345\210\227\345\274\217\346\225\260\346\215\256\345\272\223/README.md" @@ -0,0 +1,61 @@ +--- +title: 列式数据库 +date: 2023-09-08 15:58:37 +categories: + - 数据库 + - 列式数据库 +tags: + - 数据库 + - 列式数据库 +permalink: /pages/46f339/ +hidden: true +index: false +--- + +# 列式数据库 + +## 📖 内容 + +### HBase + +#### [HBase 快速入门](01.HBase/01.HBase快速入门.md) + +#### [HBase 数据模型](01.HBase/02.HBase数据模型.md) + +#### [HBase Schema 设计](01.HBase/03.HBaseSchema设计.md) + +#### [HBase 架构](01.HBase/04.HBase架构.md) + +#### [HBase Java API 基础特性](01.HBase/10.HBaseJavaApi基础特性.md) + +#### [HBase Java API 高级特性之过滤器](01.HBase/11.HBaseJavaApi高级特性之过滤器.md) + +#### [HBase Java API 高级特性之协处理器](01.HBase/12.HBaseJavaApi高级特性之协处理器.md) + +#### [HBase Java API 其他高级特性](01.HBase/13.HBaseJavaApi其他高级特性.md) + +#### [HBase 运维](01.HBase/21.HBase运维.md) + +#### [HBase 命令](01.HBase/22.HBase命令.md) + +## 📚 资料 + +### HBase 资料 + +- **官方** + - [HBase 官网](http://hbase.apache.org/) + - [HBase 官方文档](https://hbase.apache.org/book.html) + - [HBase 官方文档中文版](http://abloz.com/hbase/book.html) + - [HBase API](https://hbase.apache.org/apidocs/index.html) +- **教程** + - [BigData-Notes](https://github.com/heibaiying/BigData-Notes) +- **书籍** + - [《Hadoop 权威指南(第四版)》](https://item.jd.com/12109713.html) +- **文章** + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/bigtable-osdi06.pdf) + - [Intro to HBase](https://www.slideshare.net/alexbaranau/intro-to-hbase) + - [深入理解 Hbase 架构](https://segmentfault.com/a/1190000019959411) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" index a81581f896..be6761cbb3 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/01.Elasticsearch\351\235\242\350\257\225\346\200\273\347\273\223.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 面试总结 date: 2020-06-16 07:10:44 +order: 01 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" index 8256a5566e..7cc762b32e 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/02.Elasticsearch\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 快速入门 date: 2020-06-16 07:10:44 +order: 02 categories: - 数据库 - 搜索引擎数据库 @@ -114,7 +115,7 @@ Document 使用 JSON 格式表示,下面是一个例子。 - 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。 - `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210712104055.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210712104055.png) ### ES 读数据过程 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" index 45a9ab082b..ac45741163 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/03.Elasticsearch\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 简介 date: 2022-02-22 21:01:01 +order: 03 categories: - 数据库 - 搜索引擎数据库 @@ -122,7 +123,7 @@ Elasticsearch 是一个近乎实时的搜索平台。这意味着**从索引文 #### 倒排索引 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220108215559.PNG) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220108215559.PNG) #### index template diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" index f4ac7d861e..adbf407050 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/04.Elasticsearch\347\264\242\345\274\225.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 索引 date: 2022-02-22 21:01:01 +order: 04 categories: - 数据库 - 搜索引擎数据库 @@ -378,19 +379,19 @@ GET /twitter/tweet,user/_search?q=user:banon 我们也可以搜索多个索引下匹配条件的文档,如下: ```bash -GET /twitter,elasticsearch/_search?q=tag:wow +GET /twitter,elasticsearch/_search?q=tags:wow ``` 此外我们也可以搜索所有索引下匹配条件的文档,用\_all 表示所有索引,如下: ```bash -GET /_all/_search?q=tag:wow +GET /_all/_search?q=tags:wow ``` 甚至我们可以搜索所有索引及所有 type 下匹配条件的文档,如下: ```bash -GET /_search?q=tag:wow +GET /_search?q=tags:wow ``` ### URI 搜索 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" index acd1f5bce4..dcb818bd8d 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\230\240\345\260\204.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 映射 date: 2022-05-16 19:54:24 +order: 05 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" index 16f50b2ee8..c5d158e23f 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/05.Elasticsearch\346\237\245\350\257\242.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 查询 date: 2022-01-18 08:01:08 +order: 05 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" index 6c9e404af1..e68927b71c 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/06.Elasticsearch\351\253\230\344\272\256.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 高亮搜索及显示 date: 2022-02-22 21:01:01 +order: 06 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" index 710e34e3b5..7512f40a39 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/07.Elasticsearch\346\216\222\345\272\217.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 排序 date: 2022-01-19 22:49:16 +order: 07 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" index 451ef1268a..2516e6d0db 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/08.Elasticsearch\350\201\232\345\220\210.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 聚合 date: 2022-01-19 22:49:16 +order: 08 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" index 7f7b2a6dec..03ccb8edbd 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/09.Elasticsearch\345\210\206\346\236\220\345\231\250.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 分析器 date: 2022-02-22 21:01:01 +order: 09 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" index 0105a61f74..718674621c 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/10.Elasticsearch\346\200\247\350\203\275\344\274\230\345\214\226.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 性能优化 date: 2022-01-21 19:54:43 +order: 10 categories: - 数据库 - 搜索引擎数据库 @@ -178,7 +179,7 @@ routing 默认值是文档的 id,也可以采用自定义值,比如用户 ID - Query:此文档与此查询子句的匹配程度如何? - Filter:此文档和查询子句匹配吗? -Elasticsearch 针对 Filter 查询只需要回答「是」或者「否」,不需要像 Query 查询一样计算相关性分数,同时 Filter 结果可以缓存。 +Elasticsearch 针对 Filter 查询只需要回答“是”或者“否”,不需要像 Query 查询一样计算相关性分数,同时 Filter 结果可以缓存。 ### 深度翻页 @@ -307,4 +308,4 @@ ES 一旦创建好索引后,就无法调整分片的设置,而在 ES 中, ## 参考资料 -- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) \ No newline at end of file +- [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" index b07596efb6..68b8d2292b 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/11.ElasticsearchRestApi.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch Rest API date: 2020-06-16 07:10:44 +order: 11 categories: - 数据库 - 搜索引擎数据库 @@ -46,11 +47,11 @@ ElasticSearch Rest API 分为两种: URI Search 示例: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220530072511.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220530072511.png) Request Body Search 示例: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220530072654.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220530072654.png) ## 索引 API diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" index 509abf3338..907c670232 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md" @@ -1,6 +1,7 @@ --- title: ElasticSearch Java API 之 High Level REST Client date: 2022-03-01 18:55:46 +order: 12 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" index 56401b5bfb..a1186b559b 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/13.Elasticsearch\351\233\206\347\276\244\345\222\214\345\210\206\347\211\207.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 集群和分片 date: 2022-03-01 20:52:25 +order: 13 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" index 084843fb49..01ed0ef339 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/20.Elasticsearch\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: Elasticsearch 运维 date: 2020-06-16 07:10:44 +order: 20 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" index c7372c85b3..5f7ee875c0 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/01.Elasticsearch/README.md" @@ -11,6 +11,7 @@ tags: - Elasticsearch permalink: /pages/74675e/ hidden: true +index: false --- # Elasticsearch 教程 @@ -74,4 +75,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" index 82e6f4ee1b..d9626e6673 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/01.Elastic\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Elastic 快速入门 date: 2020-06-16 07:10:44 +order: 01 categories: - 数据库 - 搜索引擎数据库 @@ -264,10 +265,10 @@ Filebeat 将每个事件的传递状态存储在注册表文件中。所以它 ## 5. 运维 -- [ElasticSearch 运维](nosql/elasticsearch/Elasticsearch运维.md) -- [Logstash 运维](nosql/elasticsearch/elastic/elastic-logstash-ops.mdstic/elastic-logstash-ops.md) -- [Kibana 运维](nosql/elasticsearch/elastic/elastic-kibana-ops.mdlastic/elastic-kibana-ops.md) -- [Beats 运维](nosql/elasticsearch/elastic/elastic-beats-ops.mdelastic/elastic-beats-ops.md) +- [ElasticSearch 运维](../01.Elasticsearch/20.Elasticsearch运维.md) +- [Logstash 运维](07.Logstash运维.md) +- [Kibana 运维](05.Kibana运维.md) +- [Beats 运维](03.Filebeat运维.md) ## 6. 参考资料 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" index 3f03ea08fc..fccaac2c8f 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/02.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Filebeat.md" @@ -1,6 +1,7 @@ --- title: Elastic 技术栈之 Filebeat date: 2020-06-16 07:10:44 +order: 02 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" index 375f44b9c6..8ee54577e0 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/03.Filebeat\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: Filebeat 运维 date: 2020-06-16 07:10:44 +order: 03 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" index e803d546c6..b52a9c05e9 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/04.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Kibana.md" @@ -1,6 +1,7 @@ --- title: Elastic 技术栈之 Kibana date: 2020-06-16 07:10:44 +order: 04 categories: - 数据库 - 搜索引擎数据库 @@ -162,7 +163,7 @@ count:[1 TO 5] - 在 `alpha` 和 `omega` 之间的标签,不包括 `alpha` 和 `omega` ``` -tag:{alpha TO omega} +tags:{alpha TO omega} ``` - 10 以上的数字 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" index 5a81a10e12..7a159cd138 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/05.Kibana\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: Kibana 运维 date: 2020-06-16 07:10:44 +order: 05 categories: - 数据库 - 搜索引擎数据库 @@ -179,7 +180,7 @@ count:[1 TO 5] - 在 `alpha` 和 `omega` 之间的标签,不包括 `alpha` 和 `omega` ``` -tag:{alpha TO omega} +tags:{alpha TO omega} ``` - 10 以上的数字 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" index 708cb24f3b..a7cc019499 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/06.Elastic\346\212\200\346\234\257\346\240\210\344\271\213Logstash.md" @@ -1,6 +1,7 @@ --- title: Elastic 技术栈之 Logstash date: 2020-06-16 07:10:44 +order: 06 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" index 839274917b..82733fe380 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/07.Logstash\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: Logstash 运维 date: 2020-06-16 07:10:44 +order: 07 categories: - 数据库 - 搜索引擎数据库 diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" index 9e12932f11..9653203411 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/02.Elastic/README.md" @@ -11,6 +11,7 @@ tags: - Elastic permalink: /pages/7bf7f7/ hidden: true +index: false --- # Elastic 技术栈 @@ -55,4 +56,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/README.md" new file mode 100644 index 0000000000..4ba20d682b --- /dev/null +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/07.\346\220\234\347\264\242\345\274\225\346\223\216\346\225\260\346\215\256\345\272\223/README.md" @@ -0,0 +1,112 @@ +--- +title: 搜索引擎数据库 +date: 2023-09-08 16:02:29 +categories: + - 数据库 + - 搜索引擎数据库 +tags: + - 数据库 + - 搜索引擎数据库 +permalink: /pages/82c9ce/ +hidden: true +index: false +--- + +# 搜索引擎数据库 + +## 📖 内容 + +### Elasticsearch + +> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 + +#### [Elasticsearch 面试总结](01.Elasticsearch/01.Elasticsearch面试总结.md) + +#### [Elasticsearch 快速入门](01.Elasticsearch/02.Elasticsearch快速入门.md) + +#### [Elasticsearch 简介](01.Elasticsearch/03.Elasticsearch简介.md) + +#### [Elasticsearch 索引](01.Elasticsearch/04.Elasticsearch索引.md) + +#### [Elasticsearch 查询](01.Elasticsearch/05.Elasticsearch查询.md) + +#### [Elasticsearch 高亮](01.Elasticsearch/06.Elasticsearch高亮.md) + +#### [Elasticsearch 排序](01.Elasticsearch/07.Elasticsearch排序.md) + +#### [Elasticsearch 聚合](01.Elasticsearch/08.Elasticsearch聚合.md) + +#### [Elasticsearch 分析器](01.Elasticsearch/09.Elasticsearch分析器.md) + +#### [Elasticsearch 性能优化](01.Elasticsearch/10.Elasticsearch性能优化.md) + +#### [Elasticsearch Rest API](01.Elasticsearch/11.ElasticsearchRestApi.md) + +#### [ElasticSearch Java API 之 High Level REST Client](01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md) + +#### [Elasticsearch 集群和分片](01.Elasticsearch/13.Elasticsearch集群和分片.md) + +#### [Elasticsearch 运维](01.Elasticsearch/20.Elasticsearch运维.md) + +### Elastic + +#### [Elastic 快速入门](02.Elastic/01.Elastic快速入门.md) + +#### [Elastic 技术栈之 Filebeat](02.Elastic/02.Elastic技术栈之Filebeat.md) + +#### [Filebeat 运维](02.Elastic/03.Filebeat运维.md) + +#### [Elastic 技术栈之 Kibana](02.Elastic/04.Elastic技术栈之Kibana.md) + +#### [Kibana 运维](02.Elastic/05.Kibana运维.md) + +#### [Elastic 技术栈之 Logstash](02.Elastic/06.Elastic技术栈之Logstash.md) + +#### [Logstash 运维](02.Elastic/07.Logstash运维.md) + +## 📚 资料 + +### Elasticsearch 资料 + +- **官方** + - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) + - [Elasticsearch Github](https://github.com/elastic/elasticsearch) + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) + - [Elasticsearch: The Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/index.html) - ElasticSearch 官方学习资料 +- **书籍** + - [《Elasticsearch 实战》](https://book.douban.com/subject/30380439/) +- **教程** + - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) + - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) +- **文章** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + - **性能调优相关**的工程实践 + - [Elasticsearch Performance Tuning Practice at eBay](https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/) + - [Elasticsearch at Kickstarter](https://kickstarter.engineering/elasticsearch-at-kickstarter-db3c487887fc) + - [9 tips on ElasticSearch configuration for high performance](https://www.loggly.com/blog/nine-tips-configuring-elasticsearch-for-high-performance/) + - [Elasticsearch In Production - Deployment Best Practices](https://medium.com/@abhidrona/elasticsearch-deployment-best-practices-d6c1323b25d7) +- **更多资源** + - [GitHub: Awesome ElasticSearch](https://github.com/dzharii/awesome-elasticsearch) + +### Elastic 资料 + +- **官方** + - [Logstash 官网](https://www.elastic.co/cn/products/logstash) + - [Logstash Github](https://github.com/elastic/logstash) + - [Logstash 官方文档](https://www.elastic.co/guide/en/logstash/current/index.html) + - [Kibana 官网](https://www.elastic.co/cn/products/kibana) + - [Kibana Github](https://github.com/elastic/kibana) + - [Kibana 官方文档](https://www.elastic.co/guide/en/kibana/current/index.html) + - [Beats 官网](https://www.elastic.co/cn/products/beats) + - [Beats Github](https://github.com/elastic/beats) + - [Beats 官方文档](https://www.elastic.co/guide/en/beats/libbeat/current/index.html) +- **第三方工具** + - [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) +- **文章** + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/README.md" "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/README.md" index 657828f411..0d773abe68 100644 --- "a/source/_posts/12.\346\225\260\346\215\256\345\272\223/README.md" +++ "b/source/_posts/12.\346\225\260\346\215\256\345\272\223/README.md" @@ -5,13 +5,14 @@ categories: - 数据库 tags: - 数据库 -permalink: /pages/012488/ +permalink: /pages/48b310/ hidden: true +index: false ---

- logo + logo

@@ -42,160 +43,65 @@ hidden: true > - 🔁 项目同步维护:[Github](https://github.com/dunwu/db-tutorial/) | [Gitee](https://gitee.com/turnon/db-tutorial/) > - 📖 电子书阅读:[Github Pages](https://dunwu.github.io/db-tutorial/) | [Gitee Pages](https://turnon.gitee.io/db-tutorial/) -## 数据库综合 +## 目录 -### 分布式存储原理 +### [关系型数据库](03.关系型数据库) -#### 分布式理论 +- [`关系型数据库其他知识`](03.关系型数据库/99.其他) -- [分布式一致性](https://dunwu.github.io/blog/pages/dac0e2/) -- [深入剖析共识性算法 Paxos](https://dunwu.github.io/blog/pages/874539/) -- [深入剖析共识性算法 Raft](https://dunwu.github.io/blog/pages/e40812/) -- [分布式算法 Gossip](https://dunwu.github.io/blog/pages/d15993/) + [`H2 应用指南`](03.关系型数据库/99.其他/02.H2.md)、[`PostgreSQL 应用指南`](03.关系型数据库/99.其他/01.PostgreSQL.md)、[`sqlite`](03.关系型数据库/99.其他/03.Sqlite.md) -#### 分布式关键技术 +- [`关系型数据库综合知识`](03.关系型数据库/01.综合) -##### 流量调度 + [`扩展 SQL`](03.关系型数据库/01.综合/03.扩展SQL.md)、[`SQL 语法速成`](03.关系型数据库/01.综合/02.SQL语法.md)、[`SQL Cheat Sheet`](03.关系型数据库/01.综合/99.SqlCheatSheet.md) -- [流量控制](https://dunwu.github.io/blog/pages/282676/) -- [负载均衡](https://dunwu.github.io/blog/pages/98a1c1/) -- [服务路由](https://dunwu.github.io/blog/pages/d04ece/) -- [分布式会话基本原理](https://dunwu.github.io/blog/pages/3e66c2/) +- [Mysql 教程](03.关系型数据库/02.Mysql) -##### 数据调度 + [Mysql 架构](03.关系型数据库/02.Mysql/01.Mysql架构.md)、[Mysql 存储引擎](03.关系型数据库/02.Mysql/02.Mysql存储引擎.md)、[Mysql 索引](03.关系型数据库/02.Mysql/03.Mysql索引.md)、[Mysql 事务](03.关系型数据库/02.Mysql/04.Mysql事务.md)、[Mysql 锁](03.关系型数据库/02.Mysql/05.Mysql锁.md)、[Mysql 高可用](03.关系型数据库/02.Mysql/06.Mysql高可用.md)、[Mysql 优化](03.关系型数据库/02.Mysql/07.Mysql优化.md)、[Mysql 运维](03.关系型数据库/02.Mysql/20.Mysql运维.md)、[Mysql 面试](03.关系型数据库/02.Mysql/99.Mysql面试.md) -- [缓存基本原理](https://dunwu.github.io/blog/pages/471208/) -- [读写分离基本原理](https://dunwu.github.io/blog/pages/7da6ca/) -- [分库分表基本原理](https://dunwu.github.io/blog/pages/103382/) -- [分布式 ID 基本原理](https://dunwu.github.io/blog/pages/0b2e59/) -- [分布式事务基本原理](https://dunwu.github.io/blog/pages/910bad/) -- [分布式锁基本原理](https://dunwu.github.io/blog/pages/69360c/) +### [列式数据库](06.列式数据库) -### 其他 +- [`HBase 教程`](06.列式数据库/01.HBase) -- [Nosql 技术选型](01.数据库综合/01.Nosql技术选型.md) -- [数据结构与数据库索引](01.数据库综合/02.数据结构与数据库索引.md) + [`HBase 架构`](06.列式数据库/01.HBase/04.HBase架构.md)、[`HBase 快速入门`](06.列式数据库/01.HBase/01.HBase快速入门.md)、[`HBase 命令`](06.列式数据库/01.HBase/22.HBase命令.md)、[`HBase 数据模型`](06.列式数据库/01.HBase/02.HBase数据模型.md)、[`HBase 运维`](06.列式数据库/01.HBase/21.HBase运维.md)、[`HBase Java API 高级特性之过滤器`](06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md)、[`HBase Java API 高级特性之协处理器`](06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md)、[`HBase Java API 管理功能`](06.列式数据库/01.HBase/14.HBaseJavaApi管理功能.md)、[`HBase Java API 基础特性`](06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md)、[`HBase Java API 其他高级特性`](06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md)、[`HBase Schema 设计`](06.列式数据库/01.HBase/03.HBaseSchema设计.md) -## 数据库中间件 +- [`Cassandra`](06.列式数据库/02.Cassandra.md) -- [ShardingSphere 简介](02.数据库中间件/01.Shardingsphere/01.ShardingSphere简介.md) -- [ShardingSphere Jdbc](02.数据库中间件/01.Shardingsphere/02.ShardingSphereJdbc.md) -- [版本管理中间件 Flyway](02.数据库中间件/02.Flyway.md) +### [数据库中间件](02.数据库中间件) -## 关系型数据库 +- [`Shardingsphere`](02.数据库中间件/01.Shardingsphere) -> [关系型数据库](03.关系型数据库) 整理主流关系型数据库知识点。 + [`ShardingSphere 简介`](02.数据库中间件/01.Shardingsphere/01.ShardingSphere简介.md)、[`ShardingSphere Jdbc`](02.数据库中间件/01.Shardingsphere/02.ShardingSphereJdbc.md) -### 关系型数据库综合 +- [`版本管理中间件 Flyway`](02.数据库中间件/02.Flyway.md) -- [关系型数据库面试总结](03.关系型数据库/01.综合/01.关系型数据库面试.md) 💯 -- [SQL 语法基础特性](03.关系型数据库/01.综合/02.SQL语法基础特性.md) -- [SQL 语法高级特性](03.关系型数据库/01.综合/03.SQL语法高级特性.md) -- [扩展 SQL](03.关系型数据库/01.综合/03.扩展SQL.md) -- [SQL Cheat Sheet](03.关系型数据库/01.综合/99.SqlCheatSheet.md) +### [数据库综合](01.数据库综合) -### Mysql +[`Nosql 技术选型`](01.数据库综合/01.Nosql技术选型.md)、[`数据结构与数据库索引`](01.数据库综合/02.数据结构与数据库索引.md) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200716103611.png) +### [搜索引擎数据库](07.搜索引擎数据库) -- [Mysql 应用指南](03.关系型数据库/02.Mysql/01.Mysql应用指南.md) ⚡ -- [Mysql 工作流](03.关系型数据库/02.Mysql/02.MySQL工作流.md) - 关键词:`连接`、`缓存`、`语法分析`、`优化`、`执行引擎`、`redo log`、`bin log`、`两阶段提交` -- [Mysql 事务](03.关系型数据库/02.Mysql/03.Mysql事务.md) - 关键词:`ACID`、`AUTOCOMMIT`、`事务隔离级别`、`死锁`、`分布式事务` -- [Mysql 锁](03.关系型数据库/02.Mysql/04.Mysql锁.md) - 关键词:`乐观锁`、`表级锁`、`行级锁`、`意向锁`、`MVCC`、`Next-key 锁` -- [Mysql 索引](03.关系型数据库/02.Mysql/05.Mysql索引.md) - 关键词:`Hash`、`B 树`、`聚簇索引`、`回表` -- [Mysql 性能优化](03.关系型数据库/02.Mysql/06.Mysql性能优化.md) -- [Mysql 运维](03.关系型数据库/02.Mysql/20.Mysql运维.md) 🔨 -- [Mysql 配置](03.关系型数据库/02.Mysql/21.Mysql配置.md) 🔨 -- [Mysql 问题](03.关系型数据库/02.Mysql/99.Mysql常见问题.md) +- [`Elastic 技术栈`](07.搜索引擎数据库/02.Elastic) -### 其他 + [`Elastic 技术栈之 Filebeat`](07.搜索引擎数据库/02.Elastic/02.Elastic技术栈之Filebeat.md)、[`Elastic 技术栈之 Kibana`](07.搜索引擎数据库/02.Elastic/04.Elastic技术栈之Kibana.md)、[`Elastic 技术栈之 Logstash`](07.搜索引擎数据库/02.Elastic/06.Elastic技术栈之Logstash.md)、[`Elastic 快速入门`](07.搜索引擎数据库/02.Elastic/01.Elastic快速入门.md)、[`Filebeat 运维`](07.搜索引擎数据库/02.Elastic/03.Filebeat运维.md)、[`Kibana 运维`](07.搜索引擎数据库/02.Elastic/05.Kibana运维.md)、[`Logstash 运维`](07.搜索引擎数据库/02.Elastic/07.Logstash运维.md) -- [PostgreSQL 应用指南](03.关系型数据库/99.其他/01.PostgreSQL.md) -- [H2 应用指南](03.关系型数据库/99.其他/02.H2.md) -- [SqLite 应用指南](03.关系型数据库/99.其他/03.Sqlite.md) +- [`Elasticsearch 教程`](07.搜索引擎数据库/01.Elasticsearch) -## 文档数据库 + [`Elasticsearch 查询`](07.搜索引擎数据库/01.Elasticsearch/05.Elasticsearch查询.md)、[`Elasticsearch 分析器`](07.搜索引擎数据库/01.Elasticsearch/09.Elasticsearch分析器.md)、[`Elasticsearch 高亮搜索及显示`](07.搜索引擎数据库/01.Elasticsearch/06.Elasticsearch高亮.md)、[`Elasticsearch 集群和分片`](07.搜索引擎数据库/01.Elasticsearch/13.Elasticsearch集群和分片.md)、[`Elasticsearch 简介`](07.搜索引擎数据库/01.Elasticsearch/03.Elasticsearch简介.md)、[`Elasticsearch 聚合`](07.搜索引擎数据库/01.Elasticsearch/08.Elasticsearch聚合.md)、[`Elasticsearch 快速入门`](07.搜索引擎数据库/01.Elasticsearch/02.Elasticsearch快速入门.md)、[`Elasticsearch 面试总结`](07.搜索引擎数据库/01.Elasticsearch/01.Elasticsearch面试总结.md)、[`Elasticsearch 排序`](07.搜索引擎数据库/01.Elasticsearch/07.Elasticsearch排序.md)、[`Elasticsearch 索引`](07.搜索引擎数据库/01.Elasticsearch/04.Elasticsearch索引.md)、[`Elasticsearch 性能优化`](07.搜索引擎数据库/01.Elasticsearch/10.Elasticsearch性能优化.md)、[`Elasticsearch 映射`](07.搜索引擎数据库/01.Elasticsearch/05.Elasticsearch映射.md)、[`Elasticsearch 运维`](07.搜索引擎数据库/01.Elasticsearch/20.Elasticsearch运维.md)、[`ElasticSearch Java API 之 High Level REST Client`](07.搜索引擎数据库/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md)、[`Elasticsearch Rest API`](07.搜索引擎数据库/01.Elasticsearch/11.ElasticsearchRestApi.md) -### MongoDB +### [文档数据库](04.文档数据库) -> MongoDB 是一个基于文档的分布式数据库,由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 -> -> MongoDB 是一个介于关系型数据库和非关系型数据库之间的产品。它是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较复杂的数据类型。 -> -> MongoDB 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。 - -- [MongoDB 应用指南](04.文档数据库/01.MongoDB/01.MongoDB应用指南.md) -- [MongoDB 的 CRUD 操作](04.文档数据库/01.MongoDB/02.MongoDB的CRUD操作.md) -- [MongoDB 聚合操作](04.文档数据库/01.MongoDB/03.MongoDB的聚合操作.md) -- [MongoDB 事务](04.文档数据库/01.MongoDB/04.MongoDB事务.md) -- [MongoDB 建模](04.文档数据库/01.MongoDB/05.MongoDB建模.md) -- [MongoDB 建模示例](04.文档数据库/01.MongoDB/06.MongoDB建模示例.md) -- [MongoDB 索引](04.文档数据库/01.MongoDB/07.MongoDB索引.md) -- [MongoDB 复制](04.文档数据库/01.MongoDB/08.MongoDB复制.md) -- [MongoDB 分片](04.文档数据库/01.MongoDB/09.MongoDB分片.md) -- [MongoDB 运维](04.文档数据库/01.MongoDB/20.MongoDB运维.md) - -## KV 数据库 - -### Redis - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200713105627.png) - -- [Redis 面试总结](05.KV数据库/01.Redis/01.Redis面试总结.md) 💯 -- [Redis 应用指南](05.KV数据库/01.Redis/02.Redis应用指南.md) ⚡ - 关键词:`内存淘汰`、`事件`、`事务`、`管道`、`发布与订阅` -- [Redis 数据类型和应用](05.KV数据库/01.Redis/03.Redis数据类型和应用.md) - 关键词:`STRING`、`HASH`、`LIST`、`SET`、`ZSET`、`BitMap`、`HyperLogLog`、`Geo` -- [Redis 持久化](05.KV数据库/01.Redis/04.Redis持久化.md) - 关键词:`RDB`、`AOF`、`SAVE`、`BGSAVE`、`appendfsync` -- [Redis 复制](05.KV数据库/01.Redis/05.Redis复制.md) - 关键词:`SLAVEOF`、`SYNC`、`PSYNC`、`REPLCONF ACK` -- [Redis 哨兵](05.KV数据库/01.Redis/06.Redis哨兵.md) - 关键词:`Sentinel`、`PING`、`INFO`、`Raft` -- [Redis 集群](05.KV数据库/01.Redis/07.Redis集群.md) - 关键词:`CLUSTER MEET`、`Hash slot`、`MOVED`、`ASK`、`SLAVEOF no one`、`redis-trib` -- [Redis 实战](05.KV数据库/01.Redis/08.Redis实战.md) - 关键词:`缓存`、`分布式锁`、`布隆过滤器` -- [Redis 运维](05.KV数据库/01.Redis/20.Redis运维.md) 🔨 - 关键词:`安装`、`命令`、`集群`、`客户端` - -## 列式数据库 - -### HBase - -- [HBase 快速入门](06.列式数据库/01.HBase/01.HBase快速入门.md) -- [HBase 数据模型](06.列式数据库/01.HBase/02.HBase数据模型.md) -- [HBase Schema 设计](06.列式数据库/01.HBase/03.HBaseSchema设计.md) -- [HBase 架构](06.列式数据库/01.HBase/04.HBase架构.md) -- [HBase Java API 基础特性](06.列式数据库/01.HBase/10.HBaseJavaApi基础特性.md) -- [HBase Java API 高级特性之过滤器](06.列式数据库/01.HBase/11.HBaseJavaApi高级特性之过滤器.md) -- [HBase Java API 高级特性之协处理器](06.列式数据库/01.HBase/12.HBaseJavaApi高级特性之协处理器.md) -- [HBase Java API 其他高级特性](06.列式数据库/01.HBase/13.HBaseJavaApi其他高级特性.md) -- [HBase 运维](06.列式数据库/01.HBase/21.HBase运维.md) -- [HBase 命令](06.列式数据库/01.HBase/22.HBase命令.md) - -## 搜索引擎数据库 - -### Elasticsearch - -> Elasticsearch 是一个基于 Lucene 的搜索和数据分析工具,它提供了一个分布式服务。Elasticsearch 是遵从 Apache 开源条款的一款开源产品,是当前主流的企业级搜索引擎。 - -- [Elasticsearch 面试总结](07.搜索引擎数据库/01.Elasticsearch/01.Elasticsearch面试总结.md) 💯 -- [Elasticsearch 快速入门](07.搜索引擎数据库/01.Elasticsearch/02.Elasticsearch快速入门.md) -- [Elasticsearch 简介](07.搜索引擎数据库/01.Elasticsearch/03.Elasticsearch简介.md) -- [Elasticsearch 索引](07.搜索引擎数据库/01.Elasticsearch/04.Elasticsearch索引.md) -- [Elasticsearch 查询](07.搜索引擎数据库/01.Elasticsearch/05.Elasticsearch查询.md) -- [Elasticsearch 高亮](07.搜索引擎数据库/01.Elasticsearch/06.Elasticsearch高亮.md) -- [Elasticsearch 排序](07.搜索引擎数据库/01.Elasticsearch/07.Elasticsearch排序.md) -- [Elasticsearch 聚合](07.搜索引擎数据库/01.Elasticsearch/08.Elasticsearch聚合.md) -- [Elasticsearch 分析器](07.搜索引擎数据库/01.Elasticsearch/09.Elasticsearch分析器.md) -- [Elasticsearch 性能优化](07.搜索引擎数据库/01.Elasticsearch/10.Elasticsearch性能优化.md) -- [Elasticsearch Rest API](07.搜索引擎数据库/01.Elasticsearch/11.ElasticsearchRestApi.md) -- [ElasticSearch Java API 之 High Level REST Client](07.搜索引擎数据库/01.Elasticsearch/12.ElasticsearchHighLevelRestJavaApi.md) -- [Elasticsearch 集群和分片](07.搜索引擎数据库/01.Elasticsearch/13.Elasticsearch集群和分片.md) -- [Elasticsearch 运维](07.搜索引擎数据库/01.Elasticsearch/20.Elasticsearch运维.md) - -### Elastic - -- [Elastic 快速入门](07.搜索引擎数据库/02.Elastic/01.Elastic快速入门.md) -- [Elastic 技术栈之 Filebeat](07.搜索引擎数据库/02.Elastic/02.Elastic技术栈之Filebeat.md) -- [Filebeat 运维](07.搜索引擎数据库/02.Elastic/03.Filebeat运维.md) -- [Elastic 技术栈之 Kibana](07.搜索引擎数据库/02.Elastic/04.Elastic技术栈之Kibana.md) -- [Kibana 运维](07.搜索引擎数据库/02.Elastic/05.Kibana运维.md) -- [Elastic 技术栈之 Logstash](07.搜索引擎数据库/02.Elastic/06.Elastic技术栈之Logstash.md) -- [Logstash 运维](07.搜索引擎数据库/02.Elastic/07.Logstash运维.md) +- [`MongoDB 教程`](04.文档数据库/01.MongoDB) + + [`MongoDB 的 CRUD 操作`](04.文档数据库/01.MongoDB/02.MongoDB的CRUD操作.md)、[`MongoDB 的聚合操作`](04.文档数据库/01.MongoDB/03.MongoDB的聚合操作.md)、[`MongoDB 分片`](04.文档数据库/01.MongoDB/09.MongoDB分片.md)、[`MongoDB 复制`](04.文档数据库/01.MongoDB/08.MongoDB复制.md)、[`MongoDB 建模`](04.文档数据库/01.MongoDB/05.MongoDB建模.md)、[`MongoDB 建模示例`](04.文档数据库/01.MongoDB/06.MongoDB建模示例.md)、[`MongoDB 事务`](04.文档数据库/01.MongoDB/04.MongoDB事务.md)、[`MongoDB 索引`](04.文档数据库/01.MongoDB/07.MongoDB索引.md)、[`MongoDB 应用指南`](04.文档数据库/01.MongoDB/01.MongoDB应用指南.md)、[`MongoDB 运维`](04.文档数据库/01.MongoDB/20.MongoDB运维.md) + +### [KV 数据库](05.KV数据库) + +- [**Redis 教程**](05.KV数据库/01.Redis) + + [Redis 基本数据类型](05.KV数据库/01.Redis/01.Redis基本数据类型.md)、[Redis 高级数据类型](05.KV数据库/01.Redis/02.Redis高级数据类型.md)、[Redis 数据结构](05.KV数据库/01.Redis/03.Redis数据结构.md)、[Redis 过期删除和内存淘汰](05.KV数据库/01.Redis/11.Redis过期删除和内存淘汰.md)、[Redis 持久化](05.KV数据库/01.Redis/12.Redis持久化.md)、[Redis 事件](05.KV数据库/01.Redis/13.Redis事件.md)、[Redis 复制](05.KV数据库/01.Redis/21.Redis复制.md)、[Redis 哨兵](05.KV数据库/01.Redis/22.Redis哨兵.md)、[Redis 集群](05.KV数据库/01.Redis/23.Redis集群.md)、[Redis 发布订阅](05.KV数据库/01.Redis/31.Redis发布订阅.md)、[Redis 独立功能](05.KV数据库/01.Redis/32.Redis事务.md)、[Redis 管道](05.KV数据库/01.Redis/33.Redis管道.md)、[Redis 脚本](05.KV数据库/01.Redis/34.Redis脚本.md)、[Redis 运维](05.KV数据库/01.Redis/41.Redis运维.md)、[Redis 实战](05.KV数据库/01.Redis/42.Redis实战.md)、[Redis 面试](05.KV数据库/01.Redis/99.Redis面试.md) + +- [Memcached 快速入门](05.KV数据库/02.Memcached.md) ## 资料 📚 @@ -205,8 +111,8 @@ hidden: true - **书籍** - [《数据密集型应用系统设计》](https://book.douban.com/subject/30329536/) - 这可能是目前最好的分布式存储书籍,强力推荐【进阶】 - **教程** - - [CMU 15445 数据库基础课程](https://15445.courses.cs.cmu.edu/fall2019/schedule.html) - - [CMU 15721 数据库高级课程](https://15721.courses.cs.cmu.edu/spring2020/schedule.html) + - [CMU 15445 数据库基础课程](https://15445.courses.cs.cmu.edu/fall2019/schedule.md) + - [CMU 15721 数据库高级课程](https://15721.courses.cs.cmu.edu/spring2020/schedule.md) - [检索技术核心 20 讲](https://time.geekbang.org/column/intro/100048401) - 极客教程【进阶】 - [后端存储实战课](https://time.geekbang.org/column/intro/100046801) - 极客教程【入门】:讲解存储在电商领域的种种应用和一些基本特性 - **论文** @@ -243,10 +149,11 @@ hidden: true - [《MySQL 技术内幕:InnoDB 存储引擎》](https://book.douban.com/subject/24708143/) - [《MySQL 必知必会》](https://book.douban.com/subject/3354490/) - Mysql 的基本概念和语法【入门】 - **教程** - - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.html) - 入门级 SQL 教程 + - [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/139) + - [runoob.com MySQL 教程](http://www.runoob.com/mysql/mysql-tutorial.md) - 入门级 SQL 教程 - [mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial) - **文章** - - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) + - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.md) - [Some study on database storage internals](https://medium.com/@kousiknath/data-structures-database-storage-internals-1f5ed3619d43) - [Sharding Pinterest: How we scaled our MySQL fleet](https://medium.com/@Pinterest_Engineering/sharding-pinterest-how-we-scaled-our-mysql-fleet-3f341e96ca6f) - [Guide to MySQL High Availability](https://www.mysql.com/cn/why-mysql/white-papers/mysql-guide-to-high-availability-solutions/) @@ -300,7 +207,7 @@ hidden: true - 沃尔玛实验室有两篇文章值得一读。 - [Avoid Pitfalls in Scaling Cassandra Cluster at Walmart](https://medium.com/walmartlabs/avoid-pitfalls-in-scaling-your-cassandra-cluster-lessons-and-remedies-a71ca01f8c04) - [Storing Images in Cassandra at Walmart](https://medium.com/walmartlabs/building-object-store-storing-images-in-cassandra-walmart-scale-a6b9c02af593) -- [Yelp: How We Scaled Our Ad Analytics with Apache Cassandra](https://engineeringblog.yelp.com/2016/08/how-we-scaled-our-ad-analytics-with-cassandra.html) ,Yelp 的这篇博客也有一些相关的经验和教训。 +- [Yelp: How We Scaled Our Ad Analytics with Apache Cassandra](https://engineeringblog.yelp.com/2016/08/how-we-scaled-our-ad-analytics-with-cassandra.md) ,Yelp 的这篇博客也有一些相关的经验和教训。 - [Discord: How Discord Stores Billions of Messages](https://blog.discordapp.com/how-discord-stores-billions-of-messages-7fa6ec7ee4c7) ,Discord 公司分享的一个如何存储十亿级消息的技术文章。 - [Cassandra at Instagram](https://www.slideshare.net/DataStax/cassandra-at-instagram-2016) ,Instagram 的一个 PPT,其中介绍了 Instagram 中是怎么使用 Cassandra 的。 - [Netflix: Benchmarking Cassandra Scalability on AWS - Over a million writes per second](https://medium.com/netflix-techblog/benchmarking-cassandra-scalability-on-aws-over-a-million-writes-per-second-39f45f066c9e) ,Netflix 公司在 AWS 上给 Cassandra 做的一个 Benchmark。 @@ -309,8 +216,8 @@ hidden: true - [Imgur Notification: From MySQL to HBASE](https://medium.com/imgur-engineering/imgur-notifications-from-mysql-to-hbase-9dba6fc44183) - [Pinterest: Improving HBase Backup Efficiency](https://medium.com/@Pinterest_Engineering/improving-hbase-backup-efficiency-at-pinterest-86159da4b954) -- [IBM : Tuning HBase performance](https://www.ibm.com/support/knowledgecenter/en/SSPT3X_2.1.2/com.ibm.swg.im.infosphere.biginsights.analyze.doc/doc/bigsql_TuneHbase.html) -- [HBase File Locality in HDFS](http://www.larsgeorge.com/2010/05/hbase-file-locality-in-hdfs.html) +- [IBM : Tuning HBase performance](https://www.ibm.com/support/knowledgecenter/en/SSPT3X_2.1.2/com.ibm.swg.im.infosphere.biginsights.analyze.doc/doc/bigsql_TuneHbase.md) +- [HBase File Locality in HDFS](http://www.larsgeorge.com/2010/05/hbase-file-locality-in-hdfs.md) - [Apache Hadoop Goes Realtime at Facebook](http://borthakur.com/ftp/RealtimeHadoopSigmod2011.pdf) - [Storage Infrastructure Behind Facebook Messages: Using HBase at Scale](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.294.8459&rep=rep1&type=pdf) - [GitHub: Awesome HBase](https://github.com/rayokota/awesome-hbase) @@ -318,7 +225,7 @@ hidden: true 针对于 HBase 有两本书你可以考虑一下。 - 首先,先推荐两本书,一本是偏实践的《[HBase 实战](https://book.douban.com/subject/25706541/)》,另一本是偏大而全的手册型的《[HBase 权威指南](https://book.douban.com/subject/10748460/)》。 -- 当然,你也可以看看官方的 [The Apache HBase™ Reference Guide](http://hbase.apache.org/0.94/book/book.html) +- 当然,你也可以看看官方的 [The Apache HBase™ Reference Guide](http://hbase.apache.org/0.94/book/book.md) - 另外两个列数据库: - [ClickHouse - Open Source Distributed Column Database at Yandex](https://clickhouse.yandex/) - [Scaling Redshift without Scaling Costs at GIPHY](https://engineering.giphy.com/scaling-redshift-without-scaling-costs/) @@ -333,8 +240,8 @@ hidden: true - [Redis 官方文档中文版](http://redis.cn/) - [Redis 命令参考](http://redisdoc.com/) - **书籍** - - [《Redis 实战》](https://item.jd.com/11791607.html) - - [《Redis 设计与实现》](https://item.jd.com/11486101.html) + - [《Redis 实战》](https://item.jd.com/11791607.md) + - [《Redis 设计与实现》](https://item.jd.com/11486101.md) - **源码** - [《Redis 实战》配套 Python 源码](https://github.com/josiahcarlson/redis-in-action) - **资源汇总** @@ -347,13 +254,13 @@ hidden: true - [redis 分布锁 Redisson 性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505) - **文章** - [Learn Redis the hard way (in production) at Trivago](http://tech.trivago.com/2017/01/25/learn-redis-the-hard-way-in-production/) - - [Twitter: How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances](http://highscalability.com/blog/2014/9/8/how-twitter-uses-redis-to-scale-105tb-ram-39mm-qps-10000-ins.html) + - [Twitter: How Twitter Uses Redis To Scale - 105TB RAM, 39MM QPS, 10,000+ Instances](http://highscalability.com/blog/2014/9/8/how-twitter-uses-redis-to-scale-105tb-ram-39mm-qps-10000-ins.md) - [Slack: Scaling Slack’s Job Queue - Robustly Handling Billions of Tasks in Milliseconds Using Kafka and Redis](https://slack.engineering/scaling-slacks-job-queue-687222e9d100) - [GitHub: Moving persistent data out of Redis at GitHub](https://githubengineering.com/moving-persistent-data-out-of-redis/) - [Instagram: Storing Hundreds of Millions of Simple Key-Value Pairs in Redis](https://engineering.instagram.com/storing-hundreds-of-millions-of-simple-key-value-pairs-in-redis-1091ae80f74c) - [Redis in Chat Architecture of Twitch (from 27:22)](https://www.infoq.com/presentations/twitch-pokemon) - - [Deliveroo: Optimizing Session Key Storage in Redis](https://deliveroo.engineering/2016/10/07/optimising-session-key-storage.html) - - [Deliveroo: Optimizing Redis Storage](https://deliveroo.engineering/2017/01/19/optimising-membership-queries.html) + - [Deliveroo: Optimizing Session Key Storage in Redis](https://deliveroo.engineering/2016/10/07/optimising-session-key-storage.md) + - [Deliveroo: Optimizing Redis Storage](https://deliveroo.engineering/2017/01/19/optimising-membership-queries.md) - [GitHub: Awesome Redis](https://github.com/JamzyWang/awesome-redis) ### 文档数据库资料 @@ -369,7 +276,7 @@ hidden: true - [MongoDB Github](https://github.com/mongodb/mongo) - [MongoDB 官方免费教程](https://university.mongodb.com/) - **教程** - - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.html) + - [MongoDB 教程](https://www.runoob.com/mongodb/mongodb-tutorial.md) - [MongoDB 高手课](https://time.geekbang.org/course/intro/100040001) - **数据** - [mongodb-json-files](https://github.com/ozlerhakan/mongodb-json-files) @@ -388,15 +295,15 @@ hidden: true - **官方** - [Elasticsearch 官网](https://www.elastic.co/cn/products/elasticsearch) - [Elasticsearch Github](https://github.com/elastic/elasticsearch) - - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) - - [Elasticsearch: The Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/index.html) - ElasticSearch 官方学习资料 + - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.md) + - [Elasticsearch: The Definitive Guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/index.md) - ElasticSearch 官方学习资料 - **书籍** - [《Elasticsearch 实战》](https://book.douban.com/subject/30380439/) - **教程** - [ELK Stack 权威指南](https://github.com/chenryn/logstash-best-practice-cn) - - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.html) + - [Elasticsearch 教程](https://www.knowledgedict.com/tutorial/elasticsearch-intro.md) - **文章** - - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.html) + - [Elasticsearch+Logstash+Kibana 教程](https://www.cnblogs.com/xing901022/p/4704319.md) - [ELK(Elasticsearch、Logstash、Kibana)安装和配置](https://github.com/judasn/Linux-Tutorial/blob/master/ELK-Install-And-Settings.md) - **性能调优相关**的工程实践 - [Elasticsearch Performance Tuning Practice at eBay](https://www.ebayinc.com/stories/blogs/tech/elasticsearch-performance-tuning-practice-at-ebay/) @@ -415,7 +322,7 @@ hidden: true - 接下来是一些图数据库的介绍文章。 - [Handling Billions of Edges in a Graph Database](https://www.infoq.com/presentations/graph-database-scalability) - [Neo4j case studies with Walmart, eBay, AirBnB, NASA, etc](https://neo4j.com/customers/) - - [FlockDB: Distributed Graph Database for Storing Adjacency Lists at Twitter](https://blog.twitter.com/engineering/en_us/a/2010/introducing-flockdb.html) + - [FlockDB: Distributed Graph Database for Storing Adjacency Lists at Twitter](https://blog.twitter.com/engineering/en_us/a/2010/introducing-flockdb.md) - [JanusGraph: Scalable Graph Database backed by Google, IBM and Hortonworks](https://architecht.io/google-ibm-back-new-open-source-graph-database-project-janusgraph-1d74fb78db6b) - [Amazon Neptune](https://aws.amazon.com/neptune/) @@ -432,4 +339,4 @@ hidden: true ## 传送 🚪 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/01.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/01.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225.md" index be5b254774..854be55fd9 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/01.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/01.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\351\235\242\350\257\225.md" @@ -1,6 +1,7 @@ --- title: 计算机网络面试总结 date: 2019-11-21 20:13:00 +order: 01 categories: - 网络 - 网络综合 @@ -22,14 +23,14 @@ permalink: /pages/e936ba/ > > 这是学习计算机网络知识宏观层面必须要了解的核心点。知道了这些,对于网络的体系结构就基本上了解了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/overview/network-layers.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/overview/network-layers.png) 计算机网络分层一般有三种划分体系:OSI 分层;五层协议分层;TCP/IP 协议分层。 - OSI 的七层体系结构概念清楚,理论完整,但是比较复杂且不实用,所以并不流行。 - 五层协议分层是一种折中方案,在现实中更为流行。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/overview/网络分层架构图.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/overview/网络分层架构图.png) **物理层** @@ -93,7 +94,7 @@ permalink: /pages/e936ba/ ## HTTP -> 扩展阅读:[超文本传输协议 HTTP](docs/13.网络/02.网络协议/01.HTTP.md) +> 扩展阅读:[超文本传输协议 HTTP](../02.网络协议/01.HTTP.md) ## DNS @@ -101,7 +102,7 @@ permalink: /pages/e936ba/ ## TCP/UDP -> 扩展阅读:[传输控制协议 TCP](docs/13.网络/02.网络协议/03.TCP.md),[用户数据报协议 UDP](docs/13.网络/02.网络协议/04.UDP.md) +> 扩展阅读:[传输控制协议 TCP](../02.网络协议/03.TCP.md),[用户数据报协议 UDP](../02.网络协议/04.UDP.md) ### 什么是 TCP? @@ -129,7 +130,7 @@ permalink: /pages/e936ba/ (2)什么是三次握手? -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/三次握手.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/三次握手.gif) 如上图所示,三次握手流程如下: @@ -161,7 +162,7 @@ permalink: /pages/e936ba/ 如上图所示,四次挥手流程如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/四次挥手.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/四次挥手.gif) 1. 第一次挥手 - 客户端向服务端发送一个 FIN 包,用来关闭客户端到服务端的数据传送。 2. 第二次挥手 - 服务端收到这个 FIN 包,向客户端发送一个 ACK 包,确认序号为收到的序号加 1。和 SYN 一样,一个 FIN 将占用一个序号。 @@ -187,14 +188,14 @@ TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是 滑动窗口原理是什么? -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559265819762.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559265819762.png) 1. 已发送已确认 - 数据流中最早的字节已经发送并得到确认。这些数据是站在发送端的角度来看的。上图中的 31 个字节已经发送并确认。 2. 已发送但尚未确认 - 已发送但尚未得到确认的字节。发送方在确认之前,不认为这些数据已经被处理。上图中的 32 \~ 45 字节为第 2 类。 3. 未发送而接收方已 Ready - 设备尚未将数据发出 ,但接收方根据最近一次关于发送方一次要发送多少字节确认自己有足够空间。发送方会立即尝试发送。上图中的 46 \~ 51 字节为第 3 类。 4. 未发送而接收方 Not Ready - 由于接收方 not ready,还不允许将这部分数据发出。上图中的 52 以后的字节为第 4 类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559265927658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559265927658.png) 这张图片相对于上一张图片,滑动窗口偏移了 5 个字节,意味着有 5 个已发送的字节得到了确认。 diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/02.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\346\214\207\345\215\227.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/02.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\346\214\207\345\215\227.md" index f5e551c15a..947790d313 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/02.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\346\214\207\345\215\227.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/02.\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\346\214\207\345\215\227.md" @@ -1,6 +1,7 @@ --- title: 计算机网络指南 date: 2019-02-20 22:26:00 +order: 02 categories: - 网络 - 网络综合 @@ -13,7 +14,7 @@ permalink: /pages/847c99/ > 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network.jpg) ## 💡 指南 @@ -57,7 +58,7 @@ permalink: /pages/847c99/ 计算机网络的拓扑结构可分为: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/overview/network-topological-structure.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/overview/network-topological-structure.gif) - 网型拓扑网型网(Mesh network) - 环型拓扑环型网(Ring network) @@ -88,20 +89,20 @@ permalink: /pages/847c99/ > > 这是学习计算机网络知识宏观层面必须要了解的核心点。知道了这些,对于网络的体系结构就基本上了解了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/overview/network-layers.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/overview/network-layers.png) 计算机网络分层一般有三种划分体系:OSI 分层;五层协议分层;TCP/IP 协议分层。 - OSI 的七层体系结构概念清楚,理论完整,但是比较复杂且不实用,所以并不流行。 - 五层协议分层是一种折中方案,在现实中更为流行。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/overview/网络分层架构图.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/overview/网络分层架构图.png) ### 物理层 > 物理层(Physical Layer)只接收和发送一串比特(bit)流,不考虑信息的意义和信息结构。 > -> 扩展阅读:[计算机网络之物理层](docs/13.网络/01.网络综合/11.物理层.md) +> 扩展阅读:[计算机网络之物理层](11.物理层.md) - 关键词:调制、解调、数字信号、模拟信号、通信媒介、信道复用 - 数据单元:比特流。 @@ -111,7 +112,7 @@ permalink: /pages/847c99/ > 网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,数据链路层(Data Link Layer)就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 > -> 扩展阅读:[计算机网络之数据链路层](docs/13.网络/01.网络综合/12.数据链路层.md) +> 扩展阅读:[计算机网络之数据链路层](12.数据链路层.md) - 关键词:点对点信道、广播信道、`PPP`、`CSMA/CD`、局域网、以太网、`MAC`、适配器、集线器、网桥、交换机 - 主要协议:`PPP`、`CSMA/CD` 等。 @@ -122,7 +123,7 @@ permalink: /pages/847c99/ > 网络层(network layer)为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。 > -> 扩展阅读:[计算机网络之网络层](docs/13.网络/01.网络综合/13.网络层.md) +> 扩展阅读:[计算机网络之网络层](13.网络层.md) - 关键词:`IP`、`ICMP`、`ARP`、路由 - 主要协议:`IP`。 @@ -133,7 +134,7 @@ permalink: /pages/847c99/ > 传输层(transport layer)为两台主机中进程间的通信提供通用的数据传输服务。 > -> 扩展阅读:[计算机网络之网络层](docs/13.网络/01.网络综合/14.传输层.md) +> 扩展阅读:[计算机网络之网络层](14.传输层.md) - 关键词:`UDP`、`TCP`、滑动窗口、拥塞控制、三次握手 - 主要协议:`TCP`、`UDP`。 @@ -151,7 +152,7 @@ permalink: /pages/847c99/ > 应用层(application layer)通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。 > -> 扩展阅读:[计算机网络之应用层](docs/13.网络/01.网络综合/15.应用层.md) +> 扩展阅读:[计算机网络之应用层](15.应用层.md) - 关键词:`HTTP`、`DNS`、`FTP`、`TELNET`、`DHCP` - 主要协议:`HTTP`、`DNS`、`SMTP`、`Telnet`、`FTP`、`SNMP` 等。 @@ -174,6 +175,6 @@ permalink: /pages/847c99/ - [WireShark](https://www.wireshark.org/) - [Postman](https://www.getpostman.com/) -## :door: 传送门 +## 🚪 传送 | [回首頁](https://github.com/dunwu/blog) | \ No newline at end of file diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/11.\347\211\251\347\220\206\345\261\202.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/11.\347\211\251\347\220\206\345\261\202.md" index 47eb6fa075..afb2035784 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/11.\347\211\251\347\220\206\345\261\202.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/11.\347\211\251\347\220\206\345\261\202.md" @@ -1,6 +1,7 @@ --- title: 计算机网络之物理层 date: 2019-02-20 23:06:00 +order: 11 categories: - 网络 - 网络综合 @@ -21,7 +22,7 @@ permalink: /pages/e05ae2/ ## 通信系统模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/physical/数据通信系统的模型.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/physical/数据通信系统的模型.png) 通信系统模型分为三大部分:源系统(包括信源和发送器)、传输系统、目的系统(包括信宿接收器)。 @@ -34,7 +35,7 @@ permalink: /pages/e05ae2/ ## 通信方式 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/physical/通信方式.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/physical/通信方式.jpg) 有三种通信方式: @@ -67,7 +68,7 @@ permalink: /pages/e05ae2/ ### 基本带通调制方法 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/physical/基本调制方法.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/physical/基本调制方法.png) 如果你收听过广播,一定经常听到 AM、FM 这两个关键词,这是什么意思呢?答案如下: diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/12.\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/12.\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md" index 66cd7c4a43..0978ba7a9b 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/12.\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/12.\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md" @@ -1,6 +1,7 @@ --- title: 计算机网络之数据链路层 date: 2019-02-21 14:58:00 +order: 12 categories: - 网络 - 网络综合 @@ -34,7 +35,7 @@ permalink: /pages/390718/ 为了提高传输效率,应该让数据部分长度尽可能大于首部和尾部。但是,每种链路层协议都限制了帧的数据部分长度上线——最大传送单元 MTU(Maximum Transfer Unit) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/data-link/数据链路帧.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/data-link/数据链路帧.png) ### 透明传输 @@ -42,7 +43,7 @@ permalink: /pages/390718/ 帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符。如果数据部分出现转义字符,那么就在转义字符前面再加个转义字符。在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/data-link/经过字节填充后发送的数据.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/data-link/经过字节填充后发送的数据.png) ### 差错检测 @@ -58,11 +59,11 @@ permalink: /pages/390718/ 互联网用户通常都要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和 ISP 进行通信时所使用的数据链路层协议。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/data-link/PPP协议.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/data-link/PPP协议.png) PPP(点到点协议)是为在同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目的主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/data-link/PPP帧.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/data-link/PPP帧.png) PPP 的帧格式: @@ -111,7 +112,7 @@ MAC 地址长度为 6 字节(48 位),用于唯一标识网络适配器( 一台主机拥有多少个网络适配器就有多少个 MAC 地址。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/data-link/MAC帧.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/data-link/MAC帧.png) ## 设备 diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/13.\347\275\221\347\273\234\345\261\202.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/13.\347\275\221\347\273\234\345\261\202.md" index 8a0960fe07..22f8076e48 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/13.\347\275\221\347\273\234\345\261\202.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/13.\347\275\221\347\273\234\345\261\202.md" @@ -1,6 +1,7 @@ --- title: 计算机网络之网络层 date: 2019-02-25 18:05:00 +order: 13 categories: - 网络 - 网络综合 @@ -38,7 +39,7 @@ permalink: /pages/42c7a1/ - 网际控制报文协议 ICMP(Internet Control Message Protocol) - 网际组管理协议 IGMP(Internet Group Management Protocol) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1550912617336.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1550912617336.png) ### 分类的 IP 地址 @@ -56,7 +57,7 @@ IP 地址的编址方式经历了三个历史阶段: IP 地址 ::= {< 网络号 >, < 主机号 >} ``` -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086738403.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086738403.png) #### 子网划分 @@ -88,16 +89,16 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为 ### IP 地址与物理地址 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551088476626.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551088476626.png) - 物理地址是数据链路层和物理层使用的地址。 - IP 地址是网络层和以上各层使用的地址,是一种逻辑地址。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551088631948.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551088631948.png) ### IP 数据报格式 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1550913213250.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1550913213250.png) - **版本** - 有 4(IPv4)和 6(IPv6)两个值。 - **首部长度** - 占 4 位,因此最大十进制数值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 @@ -109,33 +110,33 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为 - **标识** - 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 - **片偏移** - 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1550913364479.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1550913364479.png) ## 地址解析协议 ARP 网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086787261.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086787261.png) ARP 实现由 IP 地址得到 MAC 地址。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086769846.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086769846.png) 每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。 如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086833117.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086833117.png) ## 网际控制报文协议 ICMP ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086857345.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086857345.png) ICMP 报文分为差错报告报文和询问报文。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086870897.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086870897.png) ### Ping @@ -168,7 +169,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086901339.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086901339.png) ## 网络地址转换 NAT @@ -182,7 +183,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086930371.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086930371.png) ## 路由器分组转发流程 @@ -193,7 +194,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。 - 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; - 报告转发分组出错。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086952828.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086952828.png) ## 路由选择协议 @@ -250,4 +251,4 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。 每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/network/1551086977310.png) \ No newline at end of file +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/network/1551086977310.png) \ No newline at end of file diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/14.\344\274\240\350\276\223\345\261\202.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/14.\344\274\240\350\276\223\345\261\202.md" index 3d66f296c5..ff4ef6c0ab 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/14.\344\274\240\350\276\223\345\261\202.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/14.\344\274\240\350\276\223\345\261\202.md" @@ -1,6 +1,7 @@ --- title: 计算机网络之传输层 date: 2019-02-25 20:27:00 +order: 14 categories: - 网络 - 网络综合 @@ -22,13 +23,13 @@ permalink: /pages/1d6f56/ ## UDP 首部格式 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551092392065.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551092392065.png) 首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。 ## TCP 首部格式 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551092419042.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551092419042.png) - **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。 @@ -46,7 +47,7 @@ permalink: /pages/1d6f56/ ## TCP 的三次握手 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551092794258.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551092794258.png) 假设 A 为客户端,B 为服务器端。 @@ -68,7 +69,7 @@ permalink: /pages/1d6f56/ ## TCP 的四次挥手 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551092825974.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551092825974.png) 以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 @@ -112,7 +113,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 ## TCP 滑动窗口 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551092841802.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551092841802.png) 窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。 @@ -130,7 +131,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文 如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551092981695.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551092981695.png) TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。 @@ -141,7 +142,7 @@ TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、 - 接收方有足够大的接收缓存,因此不会发生流量控制; - 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551093119265.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551093119265.png) ### 慢开始与拥塞避免 @@ -161,4 +162,4 @@ TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、 慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/1551093167163.png) \ No newline at end of file +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/1551093167163.png) \ No newline at end of file diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/15.\345\272\224\347\224\250\345\261\202.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/15.\345\272\224\347\224\250\345\261\202.md" index 7e0717f8a4..60aa22f3ab 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/15.\345\272\224\347\224\250\345\261\202.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/15.\345\272\224\347\224\250\345\261\202.md" @@ -1,6 +1,7 @@ --- title: 计算机网络之应用层 date: 2019-02-25 20:27:00 +order: 15 categories: - 网络 - 网络综合 @@ -14,7 +15,7 @@ permalink: /pages/267818/ ## HTTP -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/application/1551096916007.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/application/1551096916007.png) 超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网的数据通信的基础。 @@ -28,11 +29,11 @@ permalink: /pages/267818/ 域名服务器 DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/application/1551094759786.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/application/1551094759786.png) 域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/application/1551094954067.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/application/1551094954067.png) DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输: @@ -50,7 +51,7 @@ DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大 根据数据连接是否是服务器端主动建立,FTP 有主动和被动两种模式: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/application/1551095440002.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/application/1551095440002.png) - 主动模式:服务器端主动建立数据连接,其中服务器端的端口号为 20,客户端的端口号随机,但是必须大于 1024,因为 0\~1023 是熟知端口号。 - 被动模式:客户端主动建立数据连接,其中客户端的端口号由客户端自己指定,服务器端的端口号随机。 @@ -72,7 +73,7 @@ DHCP 工作过程如下: 3. 如果客户端选择了某个 DHCP 服务器提供的信息,那么就发送 Request 报文给该 DHCP 服务器。 4. DHCP 服务器发送 Ack 报文,表示客户端此时可以使用提供给它的信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/application/1551095610521.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/application/1551095610521.png) ## TELNET diff --git "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/README.md" "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/README.md" index 370aa19be2..55f09d58a8 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/README.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/01.\347\275\221\347\273\234\347\273\274\345\220\210/README.md" @@ -8,6 +8,7 @@ tags: - 网络 permalink: /pages/f76ad1/ hidden: true +index: false --- # 网络综合 @@ -51,4 +52,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/01.HTTP.md" "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/01.HTTP.md" index 010ecc5d87..4021557a3b 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/01.HTTP.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/01.HTTP.md" @@ -1,6 +1,7 @@ --- title: 超文本传输协议 HTTP date: 2016-01-08 22:14:00 +order: 01 categories: - 网络 - 网络协议 @@ -23,7 +24,7 @@ permalink: /pages/d58ebc/ HTTP 是由 **IETF**(Internet Engineering Task Force,互联网工程工作小组) 和 **W3C**(World Wide Web Consortium,万维网协会) 共同合作制订的,它们发布了一系列的**RFC**(Request For Comments),其中最著名的是 RFC 2616,它定义了**HTTP /1.1**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119131949.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119131949.png) ### HTTP 协议特点 @@ -106,7 +107,7 @@ HTTP 使用统一资源标识符(Uniform Resource Identifiers, URI)来传输 客户端发送一个 HTTP 请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119132129.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119132129.png) HTTP 请求报文由以下元素组成: @@ -150,7 +151,7 @@ HTTP 请求报文由以下元素组成: ### HTTP 响应报文 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200119132311.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200119132311.png) HTTP 响应报文包含了下面的元素: diff --git "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/02.DNS.md" "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/02.DNS.md" index fa8958276c..e2c087d007 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/02.DNS.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/02.DNS.md" @@ -1,6 +1,7 @@ --- title: 域名解析协议 DNS date: 2018-10-17 18:14:00 +order: 02 categories: - 网络 - 网络协议 @@ -29,7 +30,7 @@ DNS 是一个应用层协议。 域名是由一串用点分隔符 `.` 组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的方位。域名可以说是一个 IP 地址的代称,目的是为了便于记忆后者。例如,wikipedia.org 是一个域名,和 IP 地址 208.80.152.2 相对应。人们可以直接访问 wikipedia.org 来代替 IP 地址,然后域名系统(DNS)就会将它转化成便于机器识别的 IP 地址。这样,人们只需要记忆 wikipedia.org 这一串带有特殊含义的字符,而不需要记忆没有含义的数字。 -
+
### DNS 的分层 @@ -64,7 +65,7 @@ DNS 中,常见的资源记录类型有: 通过域名去查询域名服务器,得到 IP 地址的过程叫做域名解析。在解析域名时,一般先静态域名解析,再动态解析域名。可以将一些常用的域名放入静态域名解析表中,这样可以大大提高域名解析效率。 -
+
上图展示了一个动态域名解析的流程,步骤如下: diff --git "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/03.TCP.md" "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/03.TCP.md" index e8f35126b3..79c373c901 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/03.TCP.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/03.TCP.md" @@ -1,6 +1,7 @@ --- title: 传输控制协议 TCP date: 2019-05-31 11:51:00 +order: 03 categories: - 网络 - 网络协议 @@ -19,7 +20,7 @@ permalink: /pages/5dec61/ **TCP(Transmission Control Protocol),即传输控制协议,它是一种`面向连接的`、`可靠的`、`基于字节流的`传输层通信协议**。TCP 由 RFC 793 定义。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559263786555.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559263786555.png) ### TCP 的特性 @@ -46,7 +47,7 @@ TCP 对于需要高可靠性但时间紧迫的应用程序很有用。比如包 ### TCP 报文 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559264511812.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559264511812.png) 报文字段不一一阐述,重点关注以下几点: @@ -58,11 +59,11 @@ TCP 对于需要高可靠性但时间紧迫的应用程序很有用。比如包 - **Window 又叫 Advertised-Window**,也就是著名的滑动窗口(Sliding Window),**用于解决流控的**。 - **TCP Flag**,也就是包的类型,**主要是用于操控 TCP 的状态机的**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559264593860.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559264593860.png) ## TCP 通信流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559264679371.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559264679371.png) TCP 完整的通信分为三块: @@ -78,7 +79,7 @@ TCP 完整的通信分为三块: (2)什么是三次握手? -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/三次握手.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/三次握手.gif) 如上图所示,三次握手流程如下: @@ -108,7 +109,7 @@ TCP 完整的通信分为三块: 如上图所示,四次挥手流程如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/network/transport/四次挥手.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/network/transport/四次挥手.gif) 1. 第一次挥手 - 客户端向服务端发送一个 FIN 包,用来关闭客户端到服务端的数据传送。 2. 第二次挥手 - 服务端收到这个 FIN 包,向客户端发送一个 ACK 包,确认序号为收到的序号加 1。和 SYN 一样,一个 FIN 将占用一个序号。 @@ -132,14 +133,14 @@ TCP 头里有一个字段叫 Window,又叫 Advertised-Window,这个字段是 滑动窗口原理是什么? -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559265819762.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559265819762.png) 1. 已发送已确认 - 数据流中最早的字节已经发送并得到确认。这些数据是站在发送端的角度来看的。上图中的 31 个字节已经发送并确认。 2. 已发送但尚未确认 - 已发送但尚未得到确认的字节。发送方在确认之前,不认为这些数据已经被处理。上图中的 32 \~ 45 字节为第 2 类。 3. 未发送而接收方已 Ready - 设备尚未将数据发出 ,但接收方根据最近一次关于发送方一次要发送多少字节确认自己有足够空间。发送方会立即尝试发送。上图中的 46 \~ 51 字节为第 3 类。 4. 未发送而接收方 Not Ready - 由于接收方 not ready,还不允许将这部分数据发出。上图中的 52 以后的字节为第 4 类。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559265927658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559265927658.png) 这张图片相对于上一张图片,滑动窗口偏移了 5 个字节,意味着有 5 个已发送的字节得到了确认。 diff --git "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/04.UDP.md" "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/04.UDP.md" index 7dbe0e349f..2402242e1d 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/04.UDP.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/04.UDP.md" @@ -1,6 +1,7 @@ --- title: 用户数据报协议 UDP date: 2019-05-31 11:51:00 +order: 04 categories: - 网络 - 网络协议 @@ -15,7 +16,7 @@ permalink: /pages/4eee26/ ## 简介 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559263939493.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559263939493.png) UDP 是无连接的。数据报(类似于数据包)只在数据报级别有保证。数据报可能会无序的到达目的地,也有可能会遗失。UDP 不支持拥塞控制。虽然不如 TCP 那样有保证,但 UDP 通常效率更高。 diff --git "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/05.ICMP.md" "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/05.ICMP.md" index 1872c07c88..0baa446bb9 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/05.ICMP.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/05.ICMP.md" @@ -1,6 +1,7 @@ --- title: 网络协议之 ICMP date: 2014-07-02 20:54:00 +order: 05 categories: - 网络 - 网络协议 diff --git "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/README.md" "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/README.md" index 9e48a8d58a..75ea12c821 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/README.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/02.\347\275\221\347\273\234\345\215\217\350\256\256/README.md" @@ -9,6 +9,7 @@ tags: - 网络协议 permalink: /pages/b2bc79/ hidden: true +index: false --- # 网络协议 @@ -45,4 +46,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/01.WebSocket.md" "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/01.WebSocket.md" index 69caeeb41e..6cef68aea4 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/01.WebSocket.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/01.WebSocket.md" @@ -1,6 +1,7 @@ --- title: 网络技术之 Websocket date: 2019-05-31 11:51:00 +order: 01 categories: - 网络 - 网络技术 @@ -53,10 +54,10 @@ WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 JavaScript 和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/web/ajax-long-polling.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/web/ajax-long-polling.png) 因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/spring/web/websockets-flow.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/spring/web/websockets-flow.png) #### WebSocket 如何工作 diff --git "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/02.CDN.md" "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/02.CDN.md" index fe1a4f06bf..f90e078e02 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/02.CDN.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/02.CDN.md" @@ -1,6 +1,7 @@ --- title: 网络技术之 CDN date: 2019-05-29 23:19:00 +order: 02 categories: - 网络 - 网络技术 @@ -13,7 +14,7 @@ permalink: /pages/a6febf/ # 网络技术之 CDN -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559138689425.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559138689425.png) ## 简介 @@ -52,7 +53,7 @@ CDN 是一个策略性部署的整体系统,包括**分布式存储**、**负 CDN 网络将存储资源分布到各个地理位置、各个网段。存储系统作为 CDN 系统密不可分的一部分,将 CDN 分发的文件和数据库表记录内容存储起来,提供持续服务。存储系统采用三级存储架构,包括核心存储、CDN 服务节点分布式缓存和终端本地缓存。任意一个点的存储崩溃或失效,并不影响系统服务的可用性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559140068433.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559140068433.png) 如 CDN 系统在 5 大运营商(中国电信、中国网通、中国铁通、中国移动、中国联通)以及 2 大专有网络(中国教育和科研计算机网、中国科技网)都布有 CDN 节点。**这样就消除了不同运营商之间互联的瓶颈造成的影响,实现了跨运营商的网络加速,保证不同网络中的用户都能得到良好的访问质量。** @@ -77,7 +78,7 @@ CDN 负载均衡系统实现 CDN 的**内容路由功能**。它的作用是将 ## CDN 访问流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559126750010.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559126750010.png) 1. 用户在浏览器中访问域名,域名解析的请求被发往网站的 DNS 域名解析服务器; 2. 由于网站的 DNS 域名解析服务器对此域名的解析设置了 CNAME,请求被指向 CDN 网络中的智能 DNS 负载均衡系统; diff --git "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/03.VPN.md" "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/03.VPN.md" index cb169d09a0..b2b19c92b8 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/03.VPN.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/03.VPN.md" @@ -1,6 +1,7 @@ --- title: 网络技术之 VPN date: 2020-02-03 10:56:00 +order: 03 categories: - 网络 - 网络技术 @@ -13,7 +14,7 @@ permalink: /pages/9e7fab/ # 网络技术之 VPN -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200203095528.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200203095528.png) ## 简介 @@ -27,7 +28,7 @@ VPN 属于远程访问技术,简单地说就是利用公用网络架设专用 ### 隐藏 IP 和位置 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200203100404.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200203100404.png) VPN 可以隐藏使用者的 IP 地址和位置。 @@ -43,7 +44,7 @@ VPN 可以隐藏使用者的 IP 地址和位置。 ### 通信加密 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200203100543.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200203100543.png) 使用 VPN 时,可以对信息进行加密,使得密码,电子邮件,照片,银行数据和其他敏感信息不会被拦截。 @@ -53,7 +54,7 @@ VPN 可以加密信息,使黑客更难以拦截和窃取数据。 ### 翻墙 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200203100706.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200203100706.png) 轻松解除对 Facebook 和 Twitter,Skype,YouTube 和 Gmail 等网站和服务的阻止。 即使您被告知您所在的国家/地区不可用它,或者您所在的学校或办公室网络限制访问,也可以获取所需的东西。 @@ -61,7 +62,7 @@ VPN 可以加密信息,使黑客更难以拦截和窃取数据。 ### 避免被监听 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200203100933.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200203100933.png) 使用 VPN 可以向政府、ISP、黑客隐藏通信信息。 @@ -79,7 +80,7 @@ VPN 可以加密信息,使黑客更难以拦截和窃取数据。 VPN 会在您的设备和私人服务器之间建立私人和加密的互联网连接。 这意味着您的数据无法被 ISP 或任何其他第三方读取或理解。 然后,私有服务器将您的流量发送到您要访问的网站或服务上。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200203102422.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200203102422.png) VPN 的基本处理过程如下: @@ -91,7 +92,7 @@ VPN 的基本处理过程如下: ## VPN 协议 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200203102656.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200203102656.png) - OpenVPN diff --git "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/README.md" "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/README.md" index ab3696a150..533fc7a972 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/README.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/03.\347\275\221\347\273\234\346\212\200\346\234\257/README.md" @@ -8,6 +8,7 @@ tags: - 网络 permalink: /pages/75570a/ hidden: true +index: false --- # 网络技术 @@ -42,4 +43,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/13.\347\275\221\347\273\234/README.md" "b/source/_posts/13.\347\275\221\347\273\234/README.md" index b64d84f58e..c5b20249b2 100644 --- "a/source/_posts/13.\347\275\221\347\273\234/README.md" +++ "b/source/_posts/13.\347\275\221\347\273\234/README.md" @@ -7,6 +7,7 @@ tags: - 网络 permalink: /pages/b39653/ hidden: true +index: false --- # 计算机网络 @@ -68,4 +69,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/01.Windows\345\256\236\347\224\250\346\212\200\345\267\247.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/01.Windows\345\256\236\347\224\250\346\212\200\345\267\247.md" index f83b257a32..5caa693dd4 100644 --- "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/01.Windows\345\256\236\347\224\250\346\212\200\345\267\247.md" +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/01.Windows\345\256\236\347\224\250\346\212\200\345\267\247.md" @@ -1,6 +1,7 @@ --- title: Windows 常用技巧总结 date: 2019-05-07 20:35:00 +order: 01 categories: - 操作系统 - 操作系统应用 diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/02.Mac\345\256\236\347\224\250\346\212\200\345\267\247.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/02.Mac\345\256\236\347\224\250\346\212\200\345\267\247.md" index 972d3b065d..646bf5ddc3 100644 --- "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/02.Mac\345\256\236\347\224\250\346\212\200\345\267\247.md" +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/01.\346\223\215\344\275\234\347\263\273\347\273\237\345\272\224\347\224\250/02.Mac\345\256\236\347\224\250\346\212\200\345\267\247.md" @@ -1,6 +1,7 @@ --- title: Mac 常用技巧总结 date: 2019-05-07 20:35:00 +order: 02 categories: - 操作系统 - 操作系统应用 @@ -20,11 +21,11 @@ dmg 格式:双击安装包,然后拖到 applications 文件夹下即可。 #### 更改默认搜索引擎 -选择「偏好设置--\>搜索--\>搜索引擎--\>Google」。 +选择“偏好设置--\>搜索--\>搜索引擎--\>Google”。 #### 导入 chrome 浏览器的书签 -选择「文件-->导入自--> Google Chrome」,然后选择要导入的项目。 +选择“文件-->导入自--> Google Chrome”,然后选择要导入的项目。 #### 快捷键 @@ -32,7 +33,7 @@ Command + R 刷新 #### 上方显示书签栏/收藏栏 -选择「显示--> 显示个人收藏栏」。 +选择“显示--> 显示个人收藏栏”。 #### 关闭软件的右上角通知 @@ -40,7 +41,7 @@ Command + R 刷新 #### 复制文件/文件夹路径 -- OS X 10.11 系统,选中文件夹,「cmd +Option +c」 复制文件夹路径,cmd+v 粘贴。 +- OS X 10.11 系统,选中文件夹,“cmd +Option +c” 复制文件夹路径,cmd+v 粘贴。 之前的系统,利用 Administrator 创建一个到右键菜单,然后到设置里面设置快捷键。具体操作请百度。 #### 打开来自身份不明的开发者的应用程序 @@ -51,7 +52,7 @@ Command + R 刷新 - 选择文件/文件夹按 Command+C 复制,在终端中 Command+V 粘贴即可。 -- 如果只是想在 Finder 中看到文件的路径, 并方便切换层级, Finder 内置了「显示路径栏」的功能, 并配置了快捷键(Option+Cmd+P). 如下图所示: +- 如果只是想在 Finder 中看到文件的路径, 并方便切换层级, Finder 内置了“显示路径栏”的功能, 并配置了快捷键(Option+Cmd+P). 如下图所示: 20161124-184148.png @@ -63,19 +64,19 @@ Command + R 刷新 ### Mac 同时登陆两个 QQ -在已经打开的 QQ 中,按住「command + N」即可。 +在已经打开的 QQ 中,按住“command + N”即可。 ## 系统便好设置 ### 语音播报 -打开「系统便好设置-->辅助功能-->语音」,即可设置不同国家的语言。 +打开“系统便好设置-->辅助功能-->语音”,即可设置不同国家的语言。 -勾选上图中的红框部分,可以设置全局快捷键。这样的话,在任何一个软件当中,按下「 option+esc」时,就会朗读选中的文本。 +勾选上图中的红框部分,可以设置全局快捷键。这样的话,在任何一个软件当中,按下“ option+esc”时,就会朗读选中的文本。 ### 调整字体大小 -Mac 调整字体大小:「系统偏好设置 -> 显示器 -> 缩放」。如下图: +Mac 调整字体大小:“系统偏好设置 -> 显示器 -> 缩放”。如下图: ### 如何分别设置 Mac 的鼠标和触控板的滚动方向 @@ -85,7 +86,7 @@ Mac 调整字体大小:「系统偏好设置 -> 显示器 -> 缩放」。如 ### Touch Bar 自定义 -打开「系统偏好设置-键盘」,下面有个自定义控制条。 +打开“系统偏好设置-键盘”,下面有个自定义控制条。 ### 色温调节:夜间模式 @@ -129,11 +130,11 @@ PodCast 可以在 iTunes 中收听。 ### 词典 -系统有一个自带应用「词典」,可以进行单词的查询。 +系统有一个自带应用“词典”,可以进行单词的查询。 ### 如何解决 MAC 软件(dmg,akp,app)出现程序已损坏的提示 -「xxx.app 已损坏,打不开.你应该将它移到废纸篓」,并非你安装的软件已损坏,而是 Mac 系统的安全设置问题,因为这些应用都是破解或者汉化的,那么解决方法就是临时改变 Mac 系统安全设置。 +“xxx.app 已损坏,打不开.你应该将它移到废纸篓”,并非你安装的软件已损坏,而是 Mac 系统的安全设置问题,因为这些应用都是破解或者汉化的,那么解决方法就是临时改变 Mac 系统安全设置。 出现这个问题的解决方法:修改系统配置:系统偏好设置... -> 安全性与隐私。修改为任何来源。 @@ -191,7 +192,7 @@ sudo spctl --master-disable **剪切文件**: -首先选中文件,按 Command+C 复制文件;然后按「Command + Option + V」剪切文件。 +首先选中文件,按 Command+C 复制文件;然后按“Command + Option + V”剪切文件。 备注:Command+X 只能剪切文字文本,不要混淆了。 @@ -199,27 +200,27 @@ sudo spctl --master-disable > 参考链接:[《轻松玩 Mac》第 6 期:Mac 用户必须知道的 15 组快捷键](http://v.youku.com/v_show/id_XNDE4MzM0NDgw.html) -### 「space」键:快速预览 +### “space”键:快速预览 -选中文件后, 不需要启动任何应用程序,使用「space」空格键可进行快速预览,再次按下「space」空格键取消预览。 +选中文件后, 不需要启动任何应用程序,使用“space”空格键可进行快速预览,再次按下“space”空格键取消预览。 可以预览 mp3、视频、pdf 等文件。 -我们还可以**选中多张图片**, 然后按「space」键,就可以同时对比预览多张图片。这一点,很赞。 +我们还可以**选中多张图片**, 然后按“space”键,就可以同时对比预览多张图片。这一点,很赞。 ### 改名 选中文件/文件夹后,按 enter 键,就可以改名了。 -### 「command + I」键:查看文件属性 +### “command + I”键:查看文件属性 -- 选中文件后,按「command + I」键,可以查看文件的各种属性。 +- 选中文件后,按“command + I”键,可以查看文件的各种属性。 -- 选中**文件夹**后,按「command + I」键,可以查看文件夹的大小。【荐】 +- 选中**文件夹**后,按“command + I”键,可以查看文件夹的大小。【荐】 ### 切换输入法 -「control + space」 +“control + space” ### 打开 spotlight 搜索框 @@ -231,89 +232,89 @@ Cmd+C、Cmd+V、Cmd+X、Cmd+A、Cmd+Z。 ### 翻页和光标 -- 「control + ↑」:将光标定位到文章的最开头(翻页到文档的最上方) +- “control + ↑”:将光标定位到文章的最开头(翻页到文档的最上方) -- 「control + ↓」:将光标定位到文章的最末尾(翻页到文档的最下方) +- “control + ↓”:将光标定位到文章的最末尾(翻页到文档的最下方) -- 「control + ←」:将光标定位到当前行的最左侧 +- “control + ←”:将光标定位到当前行的最左侧 -- 「control + →」:将光标定位到当前行的最右侧 +- “control + →”:将光标定位到当前行的最右侧 -### 「command + shift + Y」:将文字快速保存到便笺 +### “command + shift + Y”:将文字快速保存到便笺 -选中你想要的内容(例如文字、链接等),然后按下 command + shift + Y」,那么你选中的内容就会快速保存到系统自带的「便笺」软件中。 +选中你想要的内容(例如文字、链接等),然后按下 command + shift + Y”,那么你选中的内容就会快速保存到系统自带的“便笺”软件中。 如果你想临时性的保存一段内容,这个操作很实用。 ### 程序相关 -- 「command + Q」:快速退出程序 +- “command + Q”:快速退出程序 -- 「command + tab」:切换程序 +- “command + tab”:切换程序 -- 「command + H」:隐藏当前应用程序。这是一个有趣的快捷键。 +- “command + H”:隐藏当前应用程序。这是一个有趣的快捷键。 -- 「command + ,」:打开当前应用程序的「偏好设置」。 +- “command + ,”:打开当前应用程序的“偏好设置”。 ### 窗口相关 -- 「command + N」:新建一个当前应用程序的窗口 +- “command + N”:新建一个当前应用程序的窗口 -- 「command + `」:在当前应用程序的不同窗口之间切换【很实用】 +- “command + `”:在当前应用程序的不同窗口之间切换【很实用】 -我们知道,「command + tab」是在不同的软件之间切换。但你不知道的是,「command + `」是在同一个软件的不同窗口之间切换。 +我们知道,“command + tab”是在不同的软件之间切换。但你不知道的是,“command + `”是在同一个软件的不同窗口之间切换。 -- 「command + M」:将当前窗口最小化 +- “command + M”:将当前窗口最小化 -- 「command + W」:关闭当前窗口 +- “command + W”:关闭当前窗口 ### 浏览器相关 -- 「command + T」:浏览器中,新建一个标签 +- “command + T”:浏览器中,新建一个标签 -- 「command + W」:关闭当前标签 +- “command + W”:关闭当前标签 -* 「command + R」:强制刷新。 +* “command + R”:强制刷新。 -- 「command + L」:定位到地址栏。【重要】 +- “command + L”:定位到地址栏。【重要】 ### 截图相关 -- 「command + shift + 3」:截全屏(对整个屏幕截图)。 +- “command + shift + 3”:截全屏(对整个屏幕截图)。 ### 声音相关 -选中文字后,按住「ctrl + esc」键,会将文字进行朗读。(我发现,在触控条版的 mac 上,并没有生效) +选中文字后,按住“ctrl + esc”键,会将文字进行朗读。(我发现,在触控条版的 mac 上,并没有生效) ### Dock 栏相关 -- 「option + command + D」:隐藏 dock 栏 +- “option + command + D”:隐藏 dock 栏 ### 强制推出 > 强制退出的快捷键非常重要 -- 「option + command + esc」:打开强制退出的窗口 +- “option + command + esc”:打开强制退出的窗口 ### option 相关 > 强烈推荐 -- 「option + command + H」:隐藏除当前应用程序之外的其他应用程序 +- “option + command + H”:隐藏除当前应用程序之外的其他应用程序 -- 在文本中,按住「option」键,配合鼠标的选中,可以进行块状文字选取。 +- 在文本中,按住“option”键,配合鼠标的选中,可以进行块状文字选取。 -- 「option + command + W」:快速关闭当前应用程序的所有窗口。【很实用】 +- “option + command + W”:快速关闭当前应用程序的所有窗口。【很实用】 比如说,你一次性打开了很多文件的详情,然后就可以通过此快捷键,将这些窗口一次性关闭。 -- 「option + command + I」:查看多个文件的总的属性。 +- “option + command + I”:查看多个文件的总的属性。 -* 打开 launchpad,按住「option」键,可以快速卸载应用程序。 +* 打开 launchpad,按住“option”键,可以快速卸载应用程序。 -* 在 dock 栏,右键点击软件图标,同时按住「option」键,就可以**强制退出**该软件。【重要】 +* 在 dock 栏,右键点击软件图标,同时按住“option”键,就可以**强制退出**该软件。【重要】 -- 在 Safari 浏览器中,按住「option + command + Q」退出 Safari。等下次进入 Safari 的时候,上次退出时的网址会自动被打开。【实用】 +- 在 Safari 浏览器中,按住“option + command + Q”退出 Safari。等下次进入 Safari 的时候,上次退出时的网址会自动被打开。【实用】 ### 推荐一个软件:CheatSheet @@ -324,6 +325,6 @@ Cmd+C、Cmd+V、Cmd+X、Cmd+A、Cmd+Z。 - [Awesome Mac](https://github.com/jaywcjlove/awesome-mac) - [awesome-macos-command-line](https://github.com/herrbischoff/awesome-macos-command-line) -## :door: 传送门 +## 🚪 传送 | [回首頁](https://github.com/dunwu/blog) | \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/01.\345\221\275\344\273\244/01.Linux\345\221\275\344\273\244CheatSheet.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/01.\345\221\275\344\273\244/01.Linux\345\221\275\344\273\244CheatSheet.md" new file mode 100644 index 0000000000..7ddb3c2e9b --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/01.\345\221\275\344\273\244/01.Linux\345\221\275\344\273\244CheatSheet.md" @@ -0,0 +1,603 @@ +--- +title: Linux 命令 Cheat Sheet +date: 2023-11-27 10:57:22 +order: 1 +categories: + - 操作系统 + - Linux + - 命令 +tags: + - 操作系统 + - Linux + - 命令 +permalink: /pages/af6d52/ +--- + +# Linux 命令 Cheat Sheet + +## 常见命令分类 + +- [查看 Linux 命令帮助信息](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-help.html) - 关键词:`help`, `whatis`, `info`, `which`, `whereis`, `man` +- [Linux 文件目录管理](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-dir.html) - 关键词:`cd`, `ls`, `pwd`, `mkdir`, `rmdir`, `tree`, `touch`, `ln`, `rename`, `stat`, `file`, `chmod`, `chown`, `locate`, `find`, `cp`, `mv`, `rm` +- [Linux 文件内容查看命令](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-file.html) - 关键词:`cat`, `head`, `tail`, `more`, `less`, `sed`, `vi`, `grep` +- [Linux 文件压缩和解压](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-file-compress.html) - 关键词:`tar`, `gzip`, `zip`, `unzip` +- [Linux 用户管理](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-user.html) - 关键词:`groupadd`, `groupdel`, `groupmod`, `useradd`, `userdel`, `usermod`, `passwd`, `su`, `sudo` +- [Linux 系统管理](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-system.html) - 关键词:`reboot`, `exit`, `shutdown`, `date`, `mount`, `umount`, `ps`, `kill`, `systemctl`, `service`, `crontab` +- [Linux 网络管理](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-net.html) - 关键词:关键词:`curl`, `wget`, `telnet`, `ip`, `hostname`, `ifconfig`, `route`, `ssh`, `ssh-keygen`, `firewalld`, `iptables`, `host`, `nslookup`, `nc`/`netcat`, `ping`, `traceroute`, `netstat` +- [Linux 硬件管理](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-hardware.html) - 关键词:`df`, `du`, `top`, `free`, `iotop` +- [Linux 软件管理](https://dunwu.github.io/linux-tutorial/linux/cli/linux-cli-software.html) - 关键词:`rpm`, `yum`, `apt-get` + +## 基础 + +- 学习 Bash 的基础知识。具体地,在命令行中输入 `man bash` 并至少全文浏览一遍; 它理解起来很简单并且不冗长。其他的 shell 可能很好用,但 Bash 的功能已经足够强大并且到几乎总是可用的( 如果你*只*学习 zsh,fish 或其他的 shell 的话,在你自己的设备上会显得很方便,但过度依赖这些功能会给您带来不便,例如当你需要在服务器上工作时)。 + +- 熟悉至少一个基于文本的编辑器。通常而言 Vim (`vi`) 会是你最好的选择,毕竟在终端中编辑文本时 Vim 是最好用的工具(甚至大部分情况下 Vim 要比 Emacs、大型 IDE 或是炫酷的编辑器更好用)。 + +- 学会如何使用 `man` 命令去阅读文档。学会使用 `apropos` 去查找文档。知道有些命令并不对应可执行文件,而是在 Bash 内置好的,此时可以使用 `help` 和 `help -d` 命令获取帮助信息。你可以用 `type 命令` 来判断这个命令到底是可执行文件、shell 内置命令还是别名。 + +- 学会使用 `>` 和 `<` 来重定向输出和输入,学会使用 `|` 来重定向管道。明白 `>` 会覆盖了输出文件而 `>>` 是在文件末添加。了解标准输出 stdout 和标准错误 stderr。 + +- 学会使用通配符 `*` (或许再算上 `?` 和 `[`...`]`) 和引用以及引用中 `'` 和 `"` 的区别(后文中有一些具体的例子)。 + +- 熟悉 Bash 中的任务管理工具:`&`,**ctrl-z**,**ctrl-c**,`jobs`,`fg`,`bg`,`kill` 等。 + +- 学会使用 `ssh` 进行远程命令行登录,最好知道如何使用 `ssh-agent`,`ssh-add` 等命令来实现基础的无密码认证登录。 + +- 学会基本的文件管理工具:`ls` 和 `ls -l` (了解 `ls -l` 中每一列代表的意义),`less`,`head`,`tail` 和 `tail -f` (甚至 `less +F`),`ln` 和 `ln -s` (了解硬链接与软链接的区别),`chown`,`chmod`,`du` (硬盘使用情况概述:`du -hs *`)。 关于文件系统的管理,学习 `df`,`mount`,`fdisk`,`mkfs`,`lsblk`。知道 inode 是什么(与 `ls -i` 和 `df -i` 等命令相关)。 + +- 学习基本的网络管理工具:`ip` 或 `ifconfig`,`dig`。 + +- 学习并使用一种版本控制管理系统,例如 `git`。 + +- 熟悉正则表达式,学会使用 `grep`/`egrep`,它们的参数中 `-i`,`-o`,`-v`,`-A`,`-B` 和 `-C` 这些是很常用并值得认真学习的。 + +- 学会使用 `apt-get`,`yum`,`dnf` 或 `pacman` (具体使用哪个取决于你使用的 Linux 发行版)来查找和安装软件包。并确保你的环境中有 `pip` 来安装基于 Python 的命令行工具 (接下来提到的部分程序使用 `pip` 来安装会很方便)。 + +## 日常使用 + +- 在 Bash 中,可以通过按 **Tab** 键实现自动补全参数,使用 **ctrl-r** 搜索命令行历史记录(按下按键之后,输入关键字便可以搜索,重复按下 **ctrl-r** 会向后查找匹配项,按下 **Enter** 键会执行当前匹配的命令,而按下右方向键会将匹配项放入当前行中,不会直接执行,以便做出修改)。 + +- 在 Bash 中,可以按下 **ctrl-w** 删除你键入的最后一个单词,**ctrl-u** 可以删除行内光标所在位置之前的内容,**alt-b** 和 **alt-f** 可以以单词为单位移动光标,**ctrl-a** 可以将光标移至行首,**ctrl-e** 可以将光标移至行尾,**ctrl-k** 可以删除光标至行尾的所有内容,**ctrl-l** 可以清屏。键入 `man readline` 可以查看 Bash 中的默认快捷键。内容有很多,例如 **alt-.** 循环地移向前一个参数,而 **alt-\*** 可以展开通配符。 + +* 你喜欢的话,可以执行 `set -o vi` 来使用 vi 风格的快捷键,而执行 `set -o emacs` 可以把它改回来。 + +* 为了便于编辑长命令,在设置你的默认编辑器后(例如 `export EDITOR=vim`),**ctrl-x** **ctrl-e** 会打开一个编辑器来编辑当前输入的命令。在 vi 风格下快捷键则是 **escape-v**。 + +* 键入 `history` 查看命令行历史记录,再用 `!n`(`n` 是命令编号)就可以再次执行。其中有许多缩写,最有用的大概就是 `!$`, 它用于指代上次键入的参数,而 `!!` 可以指代上次键入的命令了(参考 man 页面中的“HISTORY EXPANSION”)。不过这些功能,你也可以通过快捷键 **ctrl-r** 和 **alt-.** 来实现。 + +* `cd` 命令可以切换工作路径,输入 `cd \~` 可以进入 home 目录。要访问你的 home 目录中的文件,可以使用前缀 `\~`(例如 `\~/.bashrc`)。在 `sh` 脚本里则用环境变量 `$HOME` 指代 home 目录的路径。 + +* 回到前一个工作路径:`cd -`。 + +* 如果你输入命令的时候中途改了主意,按下 **alt-#** 在行首添加 `#` 把它当做注释再按下回车执行(或者依次按下 **ctrl-a**, **#**, **enter**)。这样做的话,之后借助命令行历史记录,你可以很方便恢复你刚才输入到一半的命令。 + +* 使用 `xargs` ( 或 `parallel`)。他们非常给力。注意到你可以控制每行参数个数(`-L`)和最大并行数(`-P`)。如果你不确定它们是否会按你想的那样工作,先使用 `xargs echo` 查看一下。此外,使用 `-I{}` 会很方便。例如: + +```bash + find . -name '*.py' | xargs grep some_function + cat hosts | xargs -I{} ssh root@{} hostname +``` + +- `pstree -p` 以一种优雅的方式展示进程树。 + +- 使用 `pgrep` 和 `pkill` 根据名字查找进程或发送信号(`-f` 参数通常有用)。 + +- 了解你可以发往进程的信号的种类。比如,使用 `kill -STOP [pid]` 停止一个进程。使用 `man 7 signal` 查看详细列表。 + +- 使用 `nohup` 或 `disown` 使一个后台进程持续运行。 + +- 使用 `netstat -lntp` 或 `ss -plat` 检查哪些进程在监听端口(默认是检查 TCP 端口; 添加参数 `-u` 则检查 UDP 端口)或者 `lsof -iTCP -sTCP:LISTEN -P -n` (这也可以在 OS X 上运行)。 + +- `lsof` 来查看开启的套接字和文件。 + +- 使用 `uptime` 或 `w` 来查看系统已经运行多长时间。 + +- 使用 `alias` 来创建常用命令的快捷形式。例如:`alias ll='ls -latr'` 创建了一个新的命令别名 `ll`。 + +- 可以把别名、shell 选项和常用函数保存在 `\~/.bashrc`,具体看下这篇[文章](http://superuser.com/a/183980/7106)。这样做的话你就可以在所有 shell 会话中使用你的设定。 + +- 把环境变量的设定以及登陆时要执行的命令保存在 `\~/.bash_profile`。而对于从图形界面启动的 shell 和 `cron` 启动的 shell,则需要单独配置文件。 + +- 要想在几台电脑中同步你的配置文件(例如 `.bashrc` 和 `.bash_profile`),可以借助 Git。 + +- 当变量和文件名中包含空格的时候要格外小心。Bash 变量要用引号括起来,比如 `"$FOO"`。尽量使用 `-0` 或 `-print0` 选项以便用 NULL 来分隔文件名,例如 `locate -0 pattern | xargs -0 ls -al` 或 `find / -print0 -type d | xargs -0 ls -al`。如果 for 循环中循环访问的文件名含有空字符(空格、tab 等字符),只需用 `IFS=$'\n'` 把内部字段分隔符设为换行符。 + +- 在 Bash 脚本中,使用 `set -x` 去调试输出(或者使用它的变体 `set -v`,它会记录原始输入,包括多余的参数和注释)。尽可能地使用严格模式:使用 `set -e` 令脚本在发生错误时退出而不是继续运行;使用 `set -u` 来检查是否使用了未赋值的变量;试试 `set -o pipefail`,它可以监测管道中的错误。当牵扯到很多脚本时,使用 `trap` 来检测 ERR 和 EXIT。一个好的习惯是在脚本文件开头这样写,这会使它能够检测一些错误,并在错误发生时中断程序并输出信息: + +```bash + set -euo pipefail + trap "echo 'error: Script failed: see failed command above'" ERR +``` + +- 在 Bash 脚本中,子 shell(使用括号 `(...)`)是一种组织参数的便捷方式。一个常见的例子是临时地移动工作路径,代码如下: + +```bash + # do something in current dir + (cd /some/other/dir && other-command) + # continue in original dir +``` + +- 在 Bash 中,变量有许多的扩展方式。`${name:?error message}` 用于检查变量是否存在。此外,当 Bash 脚本只需要一个参数时,可以使用这样的代码 `input_file=${1:?usage: $0 input_file}`。在变量为空时使用默认值:`${name:-default}`。如果你要在之前的例子中再加一个(可选的)参数,可以使用类似这样的代码 `output_file=${2:-logfile}`,如果省略了 \$2,它的值就为空,于是 `output_file` 就会被设为 `logfile`。数学表达式:`i=$(( (i + 1) % 5 ))`。序列:`{1..10}`。截断字符串:`${var%suffix}` 和 `${var#prefix}`。例如,假设 `var=foo.pdf`,那么 `echo ${var%.pdf}.txt` 将输出 `foo.txt`。 + +- 使用括号扩展(`{`...`}`)来减少输入相似文本,并自动化文本组合。这在某些情况下会很有用,例如 `mv foo.{txt,pdf} some-dir`(同时移动两个文件),`cp somefile{,.bak}`(会被扩展成 `cp somefile somefile.bak`)或者 `mkdir -p test-{a,b,c}/subtest-{1,2,3}`(会被扩展成所有可能的组合,并创建一个目录树)。 + +- 通过使用 `<(some command)` 可以将输出视为文件。例如,对比本地文件 `/etc/hosts` 和一个远程文件: + +```bash + diff /etc/hosts <(ssh somehost cat /etc/hosts) +``` + +- 编写脚本时,你可能会想要把代码都放在大括号里。缺少右括号的话,代码就会因为语法错误而无法执行。如果你的脚本是要放在网上分享供他人使用的,这样的写法就体现出它的好处了,因为这样可以防止下载不完全代码被执行。 + +```bash +{ + # 在这里写代码 +} +``` + +- 了解 Bash 中的“here documents”,例如 `cat <logfile 2>&1` 或者 `some-command &>logfile`。通常,为了保证命令不会在标准输入里残留一个未关闭的文件句柄捆绑在你当前所在的终端上,在命令后添加 `>> 2+3 +5 +``` + +## 文件及数据处理 + +- 在当前目录下通过文件名查找一个文件,使用类似于这样的命令:`find . -iname '*something*'`。在所有路径下通过文件名查找文件,使用 `locate something` (但注意到 `updatedb` 可能没有对最近新建的文件建立索引,所以你可能无法定位到这些未被索引的文件)。 + +- 使用 [`ag`](https://github.com/ggreer/the_silver_searcher) 在源代码或数据文件里检索(`grep -r` 同样可以做到,但相比之下 `ag` 更加先进)。 + +- 将 HTML 转为文本:`lynx -dump -stdin`。 + +- Markdown,HTML,以及所有文档格式之间的转换,试试 [`pandoc`](http://pandoc.org/)。 + +- 当你要处理棘手的 XML 时候,`xmlstarlet` 算是上古时代流传下来的神器。 + +- 使用 [`jq`](http://stedolan.github.io/jq/) 处理 JSON。 + +- 使用 [`shyaml`](https://github.com/0k/shyaml) 处理 YAML。 + +- 要处理 Excel 或 CSV 文件的话,[csvkit](https://github.com/onyxfish/csvkit) 提供了 `in2csv`,`csvcut`,`csvjoin`,`csvgrep` 等方便易用的工具。 + +- 当你要处理 Amazon S3 相关的工作的时候,[`s3cmd`](https://github.com/s3tools/s3cmd) 是一个很方便的工具而 [`s4cmd`](https://github.com/bloomreach/s4cmd) 的效率更高。Amazon 官方提供的 [`aws`](https://github.com/aws/aws-cli) 以及 [`saws`](https://github.com/donnemartin/saws) 是其他 AWS 相关工作的基础,值得学习。 + +- 了解如何使用 `sort` 和 `uniq`,包括 uniq 的 `-u` 参数和 `-d` 参数,具体内容在后文单行脚本节中。另外可以了解一下 `comm`。 + +- 了解如何使用 `cut`,`paste` 和 `join` 来更改文件。很多人都会使用 `cut`,但遗忘了 `join`。 + +- 了解如何运用 `wc` 去计算新行数(`-l`),字符数(`-m`),单词数(`-w`)以及字节数(`-c`)。 + +- 了解如何使用 `tee` 将标准输入复制到文件甚至标准输出,例如 `ls -al | tee file.txt`。 + +- 要进行一些复杂的计算,比如分组、逆序和一些其他的统计分析,可以考虑使用 [`datamash`](https://www.gnu.org/software/datamash/)。 + +- 注意到语言设置(中文或英文等)对许多命令行工具有一些微妙的影响,比如排序的顺序和性能。大多数 Linux 的安装过程会将 `LANG` 或其他有关的变量设置为符合本地的设置。要意识到当你改变语言设置时,排序的结果可能会改变。明白国际化可能会使 sort 或其他命令运行效率下降*许多倍*。某些情况下(例如集合运算)你可以放心的使用 `export LC_ALL=C` 来忽略掉国际化并按照字节来判断顺序。 + +- 你可以单独指定某一条命令的环境,只需在调用时把环境变量设定放在命令的前面,例如 `TZ=Pacific/Fiji date` 可以获取斐济的时间。 + +- 了解如何使用 `awk` 和 `sed` 来进行简单的数据处理。 参阅 [One-liners](#one-liners) 获取示例。 + +- 替换一个或多个文件中出现的字符串: + +```bash + perl -pi.bak -e 's/old-string/new-string/g' my-files-*.txt +``` + +- 使用 [`repren`](https://github.com/jlevy/repren) 来批量重命名文件,或是在多个文件中搜索替换内容。(有些时候 `rename` 命令也可以批量重命名,但要注意,它在不同 Linux 发行版中的功能并不完全一样。) + +```bash + # 将文件、目录和内容全部重命名 foo -> bar: + repren --full --preserve-case --from foo --to bar . + # 还原所有备份文件 whatever.bak -> whatever: + repren --renames --from '(.*)\.bak' --to '\1' *.bak + # 用 rename 实现上述功能(若可用): + rename 's/\.bak$//' *.bak +``` + +- 根据 man 页面的描述,`rsync` 是一个快速且非常灵活的文件复制工具。它闻名于设备之间的文件同步,但其实它在本地情况下也同样有用。在安全设置允许下,用 `rsync` 代替 `scp` 可以实现文件续传,而不用重新从头开始。它同时也是删除大量文件的[最快方法](https://web.archive.org/web/20130929001850/http://linuxnote.net/jianingy/en/linux/a-fast-way-to-remove-huge-number-of-files.html)之一: + +```bash +mkdir empty && rsync -r --delete empty/ some-dir && rmdir some-dir +``` + +- 若要在复制文件时获取当前进度,可使用 `pv`,[`pycp`](https://github.com/dmerejkowsky/pycp),[`progress`](https://github.com/Xfennec/progress),`rsync --progress`。若所执行的复制为 block 块拷贝,可以使用 `dd status=progress`。 + +- 使用 `shuf` 可以以行为单位来打乱文件的内容或从一个文件中随机选取多行。 + +- 了解 `sort` 的参数。显示数字时,使用 `-n` 或者 `-h` 来显示更易读的数(例如 `du -h` 的输出)。明白排序时关键字的工作原理(`-t` 和 `-k`)。例如,注意到你需要 `-k1,1` 来仅按第一个域来排序,而 `-k1` 意味着按整行排序。稳定排序(`sort -s`)在某些情况下很有用。例如,以第二个域为主关键字,第一个域为次关键字进行排序,你可以使用 `sort -k1,1 | sort -s -k2,2`。 + +- 如果你想在 Bash 命令行中写 tab 制表符,按下 **ctrl-v** **[Tab]** 或键入 `$'\t'` (后者可能更好,因为你可以复制粘贴它)。 + +- 标准的源代码对比及合并工具是 `diff` 和 `patch`。使用 `diffstat` 查看变更总览数据。注意到 `diff -r` 对整个文件夹有效。使用 `diff -r tree1 tree2 | diffstat` 查看变更的统计数据。`vimdiff` 用于比对并编辑文件。 + +- 对于二进制文件,使用 `hd`,`hexdump` 或者 `xxd` 使其以十六进制显示,使用 `bvi`,`hexedit` 或者 `biew` 来进行二进制编辑。 + +- 同样对于二进制文件,`strings`(包括 `grep` 等工具)可以帮助在二进制文件中查找特定比特。 + +- 制作二进制差分文件(Delta 压缩),使用 `xdelta3`。 + +- 使用 `iconv` 更改文本编码。需要更高级的功能,可以使用 `uconv`,它支持一些高级的 Unicode 功能。例如,这条命令移除了所有重音符号: + +```bash + uconv -f utf-8 -t utf-8 -x '::Any-Lower; ::Any-NFD; [:Nonspacing Mark:] >; ::Any-NFC; ' < input.txt > output.txt +``` + +- 拆分文件可以使用 `split`(按大小拆分)和 `csplit`(按模式拆分)。 + +- 操作日期和时间表达式,可以用 [`dateutils`](http://www.fresse.org/dateutils/) 中的 `dateadd`、`datediff`、`strptime` 等工具。 + +- 使用 `zless`、`zmore`、`zcat` 和 `zgrep` 对压缩过的文件进行操作。 + +- 文件属性可以通过 `chattr` 进行设置,它比文件权限更加底层。例如,为了保护文件不被意外删除,可以使用不可修改标记:`sudo chattr +i /critical/directory/or/file` + +- 使用 `getfacl` 和 `setfacl` 以保存和恢复文件权限。例如: + +```bash + getfacl -R /some/path > permissions.txt + setfacl --restore=permissions.txt +``` + +- 为了高效地创建空文件,请使用 `truncate`(创建[稀疏文件](https://zh.wikipedia.org/wiki/稀疏文件)),`fallocate`(用于 ext4,xfs,btrf 和 ocfs2 文件系统),`xfs_mkfile`(适用于几乎所有的文件系统,包含在 xfsprogs 包中),`mkfile`(用于类 Unix 操作系统,比如 Solaris 和 Mac OS)。 + +## 系统调试 + +- `curl` 和 `curl -I` 可以被轻松地应用于 web 调试中,它们的好兄弟 `wget` 也是如此,或者也可以试试更潮的 [`httpie`](https://github.com/jkbrzt/httpie)。 + +- 获取 CPU 和硬盘的使用状态,通常使用使用 `top`(`htop` 更佳),`iostat` 和 `iotop`。而 `iostat -mxz 15` 可以让你获悉 CPU 和每个硬盘分区的基本信息和性能表现。 + +- 使用 `netstat` 和 `ss` 查看网络连接的细节。 + +- `dstat` 在你想要对系统的现状有一个粗略的认识时是非常有用的。然而若要对系统有一个深度的总体认识,使用 [`glances`](https://github.com/nicolargo/glances),它会在一个终端窗口中向你提供一些系统级的数据。 + +- 若要了解内存状态,运行并理解 `free` 和 `vmstat` 的输出。值得留意的是“cached”的值,它指的是 Linux 内核用来作为文件缓存的内存大小,而与空闲内存无关。 + +- Java 系统调试则是一件截然不同的事,一个可以用于 Oracle 的 JVM 或其他 JVM 上的调试的技巧是你可以运行 `kill -3 ` 同时一个完整的栈轨迹和堆概述(包括 GC 的细节)会被保存到标准错误或是日志文件。JDK 中的 `jps`,`jstat`,`jstack`,`jmap` 很有用。[SJK tools](https://github.com/aragozin/jvm-tools) 更高级。 + +- 使用 [`mtr`](http://www.bitwizard.nl/mtr/) 去跟踪路由,用于确定网络问题。 + +- 用 [`ncdu`](https://dev.yorhel.nl/ncdu) 来查看磁盘使用情况,它比寻常的命令,如 `du -sh *`,更节省时间。 + +- 查找正在使用带宽的套接字连接或进程,使用 [`iftop`](http://www.ex-parrot.com/~pdw/iftop/) 或 [`nethogs`](https://github.com/raboof/nethogs)。 + +- `ab` 工具(Apache 中自带)可以简单粗暴地检查 web 服务器的性能。对于更复杂的负载测试,使用 `siege`。 + +- [`wireshark`](https://wireshark.org/),[`tshark`](https://www.wireshark.org/docs/wsug_html_chunked/AppToolstshark.html) 和 [`ngrep`](http://ngrep.sourceforge.net/) 可用于复杂的网络调试。 + +- 了解 `strace` 和 `ltrace`。这俩工具在你的程序运行失败、挂起甚至崩溃,而你却不知道为什么或你想对性能有个总体的认识的时候是非常有用的。注意 profile 参数(`-c`)和附加到一个运行的进程参数 (`-p`)。 + +- 了解使用 `ldd` 来检查共享库。但是[永远不要在不信任的文件上运行](http://www.catonmat.net/blog/ldd-arbitrary-code-execution/)。 + +- 了解如何运用 `gdb` 连接到一个运行着的进程并获取它的堆栈轨迹。 + +- 学会使用 `/proc`。它在调试正在出现的问题的时候有时会效果惊人。比如:`/proc/cpuinfo`,`/proc/meminfo`,`/proc/cmdline`,`/proc/xxx/cwd`,`/proc/xxx/exe`,`/proc/xxx/fd/`,`/proc/xxx/smaps`(这里的 `xxx` 表示进程的 id 或 pid)。 + +- 当调试一些之前出现的问题的时候,[`sar`](http://sebastien.godard.pagesperso-orange.fr/) 非常有用。它展示了 cpu、内存以及网络等的历史数据。 + +- 关于更深层次的系统分析以及性能分析,看看 `stap`([SystemTap](https://sourceware.org/systemtap/wiki)),[`perf`](),以及[`sysdig`](https://github.com/draios/sysdig)。 + +- 查看你当前使用的系统,使用 `uname`,`uname -a`(Unix/kernel 信息)或者 `lsb_release -a`(Linux 发行版信息)。 + +- 无论什么东西工作得很欢乐(可能是硬件或驱动问题)时可以试试 `dmesg`。 + +- 如果你删除了一个文件,但通过 `du` 发现没有释放预期的磁盘空间,请检查文件是否被进程占用: + `lsof | grep deleted | grep "filename-of-my-big-file"` + +## 单行脚本 + +一些命令组合的例子: + +- 当你需要对文本文件做集合交、并、差运算时,`sort` 和 `uniq` 会是你的好帮手。具体例子请参照代码后面的,此处假设 `a` 与 `b` 是两内容不同的文件。这种方式效率很高,并且在小文件和上 G 的文件上都能运用(注意尽管在 `/tmp` 在一个小的根分区上时你可能需要 `-T` 参数,但是实际上 `sort` 并不被内存大小约束),参阅前文中关于 `LC_ALL` 和 `sort` 的 `-u` 参数的部分。 + +```bash + sort a b | uniq > c # c 是 a 并 b + sort a b | uniq -d > c # c 是 a 交 b + sort a b b | uniq -u > c # c 是 a - b +``` + +- 使用 `grep . *`(每行都会附上文件名)或者 `head -100 *`(每个文件有一个标题)来阅读检查目录下所有文件的内容。这在检查一个充满配置文件的目录(如 `/sys`、`/proc`、`/etc`)时特别好用。 + +* 计算文本文件第三列中所有数的和(可能比同等作用的 Python 代码快三倍且代码量少三倍): + +```bash + awk '{ x += $3 } END { print x }' myfile +``` + +- 如果你想在文件树上查看大小/日期,这可能看起来像递归版的 `ls -l` 但比 `ls -lR` 更易于理解: + +```bash + find . -type f -ls +``` + +- 假设你有一个类似于 web 服务器日志文件的文本文件,并且一个确定的值只会出现在某些行上,假设一个 `acct_id` 参数在 URI 中。如果你想计算出每个 `acct_id` 值有多少次请求,使用如下代码: + +```bash + egrep -o 'acct_id=[0-9]+' access.log | cut -d= -f2 | sort | uniq -c | sort -rn +``` + +- 要持续监测文件改动,可以使用 `watch`,例如检查某个文件夹中文件的改变,可以用 `watch -d -n 2 'ls -rtlh | tail'`;或者在排查 WiFi 设置故障时要监测网络设置的更改,可以用 `watch -d -n 2 ifconfig`。 + +- 运行这个函数从这篇文档中随机获取一条技巧(解析 Markdown 文件并抽取项目): + +```bash + function taocl() { + curl -s https://raw.githubusercontent.com/jlevy/the-art-of-command-line/master/README-zh.md| + pandoc -f markdown -t html | + iconv -f 'utf-8' -t 'unicode' | + xmlstarlet fo --html --dropdtd | + xmlstarlet sel -t -v "(html/body/ul/li[count(p)>0])[$RANDOM mod last()+1]" | + xmlstarlet unesc | fmt -80 + } +``` + +## 冷门但有用 + +- `expr`:计算表达式或正则匹配 + +- `m4`:简单的宏处理器 + +- `yes`:多次打印字符串 + +- `cal`:漂亮的日历 + +- `env`:执行一个命令(脚本文件中很有用) + +- `printenv`:打印环境变量(调试时或在写脚本文件时很有用) + +- `look`:查找以特定字符串开头的单词或行 + +- `cut`,`paste` 和 `join`:数据修改 + +- `fmt`:格式化文本段落 + +- `pr`:将文本格式化成页/列形式 + +- `fold`:包裹文本中的几行 + +- `column`:将文本格式化成多个对齐、定宽的列或表格 + +- `expand` 和 `unexpand`:制表符与空格之间转换 + +- `nl`:添加行号 + +- `seq`:打印数字 + +- `bc`:计算器 + +- `factor`:分解因数 + +- [`gpg`](https://gnupg.org/):加密并签名文件 + +- `toe`:terminfo 入口列表 + +- `nc`:网络调试及数据传输 + +- `socat`:套接字代理,与 `netcat` 类似 + +- [`slurm`](https://github.com/mattthias/slurm):网络流量可视化 + +- `dd`:文件或设备间传输数据 + +- `file`:确定文件类型 + +- `tree`:以树的形式显示路径和文件,类似于递归的 `ls` + +- `stat`:文件信息 + +- `time`:执行命令,并计算执行时间 + +- `timeout`:在指定时长范围内执行命令,并在规定时间结束后停止进程 + +- `lockfile`:使文件只能通过 `rm -f` 移除 + +- `logrotate`: 切换、压缩以及发送日志文件 + +- `watch`:重复运行同一个命令,展示结果并/或高亮有更改的部分 + +- [`when-changed`](https://github.com/joh/when-changed):当检测到文件更改时执行指定命令。参阅 `inotifywait` 和 `entr`。 + +- `tac`:反向输出文件 + +- `shuf`:文件中随机选取几行 + +- `comm`:一行一行的比较排序过的文件 + +- `strings`:从二进制文件中抽取文本 + +- `tr`:转换字母 + +- `iconv` 或 `uconv`:文本编码转换 + +- `split` 和 `csplit`:分割文件 + +- `sponge`:在写入前读取所有输入,在读取文件后再向同一文件写入时比较有用,例如 `grep -v something some-file | sponge some-file` + +- `units`:将一种计量单位转换为另一种等效的计量单位(参阅 `/usr/share/units/definitions.units`) + +- `apg`:随机生成密码 + +- `xz`:高比例的文件压缩 + +- `ldd`:动态库信息 + +- `nm`:提取 obj 文件中的符号 + +- `ab` 或 [`wrk`](https://github.com/wg/wrk):web 服务器性能分析 + +- `strace`:调试系统调用 + +- [`mtr`](http://www.bitwizard.nl/mtr/):更好的网络调试跟踪工具 + +- `cssh`:可视化的并发 shell + +- `rsync`:通过 ssh 或本地文件系统同步文件和文件夹 + +- [`wireshark`](https://wireshark.org/) 和 [`tshark`](https://www.wireshark.org/docs/wsug_html_chunked/AppToolstshark.html):抓包和网络调试工具 + +- [`ngrep`](http://ngrep.sourceforge.net/):网络层的 grep + +- `host` 和 `dig`:DNS 查找 + +- `lsof`:列出当前系统打开文件的工具以及查看端口信息 + +- `dstat`:系统状态查看 + +- [`glances`](https://github.com/nicolargo/glances):高层次的多子系统总览 + +- `iostat`:硬盘使用状态 + +- `mpstat`: CPU 使用状态 + +- `vmstat`: 内存使用状态 + +- `htop`:top 的加强版 + +- `last`:登入记录 + +- `w`:查看处于登录状态的用户 + +- `id`:用户/组 ID 信息 + +- [`sar`](http://sebastien.godard.pagesperso-orange.fr/):系统历史数据 + +- [`iftop`](http://www.ex-parrot.com/~pdw/iftop/) 或 [`nethogs`](https://github.com/raboof/nethogs):套接字及进程的网络利用情况 + +- `ss`:套接字数据 + +- `dmesg`:引导及系统错误信息 + +- `sysctl`: 在内核运行时动态地查看和修改内核的运行参数 + +- `hdparm`:SATA/ATA 磁盘更改及性能分析 + +- `lsblk`:列出块设备信息:以树形展示你的磁盘以及磁盘分区信息 + +- `lshw`,`lscpu`,`lspci`,`lsusb` 和 `dmidecode`:查看硬件信息,包括 CPU、BIOS、RAID、显卡、USB 设备等 + +- `lsmod` 和 `modinfo`:列出内核模块,并显示其细节 + +- `fortune`,`ddate` 和 `sl`:额,这主要取决于你是否认为蒸汽火车和莫名其妙的名人名言是否“有用” + +## 仅限 OS X 系统 + +以下是*仅限于* OS X 系统的技巧。 + +- 用 `brew` (Homebrew)或者 `port` (MacPorts)进行包管理。这些可以用来在 OS X 系统上安装以上的大多数命令。 + +- 用 `pbcopy` 复制任何命令的输出到桌面应用,用 `pbpaste` 粘贴输入。 + +- 若要在 OS X 终端中将 Option 键视为 alt 键(例如在上面介绍的 **alt-b**、**alt-f** 等命令中用到),打开 偏好设置 -> 描述文件 -> 键盘 并勾选“使用 Option 键作为 Meta 键”。 + +- 用 `open` 或者 `open -a /Applications/Whatever.app` 使用桌面应用打开文件。 + +- Spotlight:用 `mdfind` 搜索文件,用 `mdls` 列出元数据(例如照片的 EXIF 信息)。 + +- 注意 OS X 系统是基于 BSD UNIX 的,许多命令(例如 `ps`,`ls`,`tail`,`awk`,`sed`)都和 Linux 中有微妙的不同( Linux 很大程度上受到了 System V-style Unix 和 GNU 工具影响)。你可以通过标题为 "BSD General Commands Manual" 的 man 页面发现这些不同。在有些情况下 GNU 版本的命令也可能被安装(例如 `gawk` 和 `gsed` 对应 GNU 中的 awk 和 sed )。如果要写跨平台的 Bash 脚本,避免使用这些命令(例如,考虑 Python 或者 `perl` )或者经过仔细的测试。 + +- 用 `sw_vers` 获取 OS X 的版本信息。 + +## 仅限 Windows 系统 + +以下是*仅限于* Windows 系统的技巧。 + +### 在 Winodws 下获取 Unix 工具 + +- 可以安装 [Cygwin](https://cygwin.com/) 允许你在 Microsoft Windows 中体验 Unix shell 的威力。这样的话,本文中介绍的大多数内容都将适用。 + +- 在 Windows 10 上,你可以使用 [Bash on Ubuntu on Windows](https://msdn.microsoft.com/commandline/wsl/about),它提供了一个熟悉的 Bash 环境,包含了不少 Unix 命令行工具。好处是它允许 Linux 上编写的程序在 Windows 上运行,而另一方面,Windows 上编写的程序却无法在 Bash 命令行中运行。 + +- 如果你在 Windows 上主要想用 GNU 开发者工具(例如 GCC),可以考虑 [MinGW](http://www.mingw.org/) 以及它的 [MSYS](http://www.mingw.org/wiki/msys) 包,这个包提供了例如 bash,gawk,make 和 grep 的工具。MSYS 并不包含所有可以与 Cygwin 媲美的特性。当制作 Unix 工具的原生 Windows 端口时 MinGW 将特别地有用。 + +- 另一个在 Windows 下实现接近 Unix 环境外观效果的选项是 [Cash](https://github.com/dthree/cash)。注意在此环境下只有很少的 Unix 命令和命令行可用。 + +### 实用 Windows 命令行工具 + +- 可以使用 `wmic` 在命令行环境下给大部分 Windows 系统管理任务编写脚本以及执行这些任务。 + +- Windows 实用的原生命令行网络工具包括 `ping`,`ipconfig`,`tracert`,和 `netstat`。 + +- 可以使用 `Rundll32` 命令来实现[许多有用的 Windows 任务](http://www.thewindowsclub.com/rundll32-shortcut-commands-windows) 。 + +### Cygwin 技巧 + +- 通过 Cygwin 的包管理器来安装额外的 Unix 程序。 + +- 使用 `mintty` 作为你的命令行窗口。 + +- 要访问 Windows 剪贴板,可以通过 `/dev/clipboard`。 + +- 运行 `cygstart` 以通过默认程序打开一个文件。 + +- 要访问 Windows 注册表,可以使用 `regtool`。 + +- 注意 Windows 驱动器路径 `C:\` 在 Cygwin 中用 `/cygdrive/c` 代表,而 Cygwin 的 `/` 代表 Windows 中的 `C:\cygwin`。要转换 Cygwin 和 Windows 风格的路径可以用 `cygpath`。这在需要调用 Windows 程序的脚本里很有用。 + +- 学会使用 `wmic`,你就可以从命令行执行大多数 Windows 系统管理任务,并编成脚本。 + +- 要在 Windows 下获得 Unix 的界面和体验,另一个办法是使用 [Cash](https://github.com/dthree/cash)。需要注意的是,这个环境支持的 Unix 命令和命令行参数非常少。 + +- 要在 Windows 上获取 GNU 开发者工具(比如 GCC)的另一个办法是使用 [MinGW](http://www.mingw.org/) 以及它的 [MSYS](http://www.mingw.org/wiki/msys) 软件包,该软件包提供了 bash、gawk、make、grep 等工具。然而 MSYS 提供的功能没有 Cygwin 完善。MinGW 在创建 Unix 工具的 Windows 原生移植方面非常有用。 + +## 更多资源 + +- [awesome-shell](https://github.com/alebcay/awesome-shell):一份精心组织的命令行工具及资源的列表。 +- [awesome-osx-command-line](https://github.com/herrbischoff/awesome-osx-command-line):一份针对 OS X 命令行的更深入的指南。 +- [Strict mode](http://redsymbol.net/articles/unofficial-bash-strict-mode/):为了编写更好的脚本文件。 +- [shellcheck](https://github.com/koalaman/shellcheck):一个静态 shell 脚本分析工具,本质上是 bash/sh/zsh 的 lint。 +- [Filenames and Pathnames in Shell](http://www.dwheeler.com/essays/filenames-in-shell.html):有关如何在 shell 脚本里正确处理文件名的细枝末节。 +- [Data Science at the Command Line](http://datascienceatthecommandline.com/#tools):用于数据科学的一些命令和工具,摘自同名书籍。 + +## 免责声明 + +除去特别小的工作,你编写的代码应当方便他人阅读。能力往往伴随着责任,你 _有能力_ 在 Bash 中玩一些奇技淫巧并不意味着你应该去做!;) + +## 授权条款 + +![img](http://creativecommons.org/licenses/by-sa/4.0/) + +本文使用授权协议 [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)。 diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/01.network-ops.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/01.network-ops.md" new file mode 100644 index 0000000000..96483a4565 --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/01.network-ops.md" @@ -0,0 +1,192 @@ +--- +title: Linux 典型运维应用 +date: 2018-09-30 18:05:00 +order: 01 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - 网络 +permalink: /pages/f7e766/ +--- + +# Linux 典型运维应用 + +> 💡 如果没有特殊说明,本文的案例都是针对 Centos 发行版本。 + +## 网络操作 + +### 无法访问外网域名 + +(1)在 hosts 中添加本机实际 IP 和本机实际域名的映射 + +```shell +echo "192.168.0.1 hostname" >> /etc/hosts +``` + +如果不知道本机域名,使用 `hostname` 命令查一下;如果不知道本机实际 IP,使用 `ifconfig` 查一下。 + +(2)配置信赖的 DNS 服务器 + +执行 `vi /etc/resolv.conf` ,添加以下内容: + +```shell +nameserver 114.114.114.114 +nameserver 8.8.8.8 +``` + +> 114.114.114.114 是国内老牌 DNS +> +> 8.8.8.8 是 Google DNS +> +> :point_right: 参考:[公共 DNS 哪家强](https://www.zhihu.com/question/32229915) + +(3)测试一下能否 ping 通 www.baidu.com + +### 配置网卡 + +使用 root 权限编辑 `/etc/sysconfig/network-scripts/ifcfg-eno16777736X` 文件 + +参考以下进行配置: + +```properties +TYPE=Ethernet # 网络类型:Ethernet以太网 +BOOTPROTO=none # 引导协议:自动获取、static静态、none不指定 +DEFROUTE=yes # 启动默认路由 +IPV4_FAILURE_FATAL=no # 不启用IPV4错误检测功能 +IPV6INIT=yes # 启用IPV6协议 +IPV6_AUTOCONF=yes # 自动配置IPV6地址 +IPV6_DEFROUTE=yes # 启用IPV6默认路由 +IPV6_FAILURE_FATAL=no # 不启用IPV6错误检测功能 +IPV6_PEERDNS=yes +IPV6_PEERROUTES=yes +IPV6_PRIVACY="no" + +NAME=eno16777736 # 网卡设备的别名(需要和文件名同名) +UUID=90528772-9967-46da-b401-f82b64b4acbc # 网卡设备的UUID唯一标识号 +DEVICE=eno16777736 # 网卡的设备名称 +ONBOOT=yes # 开机自动激活网卡 +IPADDR=192.168.1.199 # 网卡的固定IP地址 +PREFIX=24 # 子网掩码 +GATEWAY=192.168.1.1 # 默认网关IP地址 +DNS1=8.8.8.8 # DNS域名解析服务器的IP地址 +``` + +修改完后,执行 `systemctl restart network.service` 重启网卡服务。 + +## 自动化脚本 + +### Linux 开机自启动脚本 + +(1)在 `/etc/rc.local` 文件中添加命令 + +如果不想将脚本粘来粘去,或创建链接,可以在 `/etc/rc.local` 文件中添加启动命令 + +1. 先修改好脚本,使其所有模块都能在任意目录启动时正常执行; +2. 再在 `/etc/rc.local` 的末尾添加一行以绝对路径启动脚本的行; + +例: + +执行 `vim /etc/rc.local` 命令,输入以下内容: + +```shell +#!/bin/sh +# +# This script will be executed *after* all the other init scripts. +# You can put your own initialization stuff in here if you don't +# want to do the full Sys V style init stuff. + +touch /var/lock/subsys/local +/opt/pjt_test/test.pl +``` + +(2)在 `/etc/rc.d/init.d` 目录下添加自启动脚本 + +Linux 在 `/etc/rc.d/init.d` 下有很多的文件,每个文件都是可以看到内容的,其实都是一些 shell 脚本或者可执行二进制文件。 + +Linux 开机的时候,会加载运行 `/etc/rc.d/init.d` 目录下的程序,因此我们可以把想要自动运行的脚本放到这个目录下即可。系统服务的启动就是通过这种方式实现的。 + +(3)运行级别设置 + +简单的说,运行级就是操作系统当前正在运行的功能级别。 + +```shell +不同的运行级定义如下: +# 0 - 停机(千万不能把initdefault 设置为0 ) +# 1 - 单用户模式   进入方法#init s = init 1 +# 2 - 多用户,没有 NFS +# 3 - 完全多用户模式(标准的运行级) +# 4 - 没有用到 +# 5 - X11 多用户图形模式(xwindow) +# 6 - 重新启动 (千万不要把initdefault 设置为6 ) +``` + +这些级别在 `/etc/inittab` 文件里指定,这个文件是 init 程序寻找的主要文件,最先运行的服务是放在/etc/rc.d 目录下的文件。 + +在 `/etc` 目录下面有这么几个目录值得注意:rcS.d rc0.d rc1.d ... rc6.d (0,1... 6 代表启动级别 0 代表停止,1 代表单用户模式,2-5 代表多用户模式,6 代表重启) 它们的作用就相当于 redhat 下的 rc.d ,你可以把脚本放到 rcS.d,然后修改文件名,给它一个启动序号,如: S88mysql + +不过,最好的办法是放到相应的启动级别下面。具体作法: + +(1)先把脚本 mysql 放到 /etc/init.d 目录下 + +(2)查看当前系统的启动级别 + +```shell +$ runlevel +N 3 +``` + +(3)设定启动级别 + +```shell +# 98 为启动序号 +# 2 是系统的运行级别,可自己调整,注意不要忘了结尾的句点 +$ update-rc.d mysql start 98 2 . +``` + +现在我们到 /etc/rc2.d 下,就多了一个 S98mysql 这样的符号链接。 + +(4)重启系统,验证设置是否有效。 + +(5)移除符号链接 + +当你需要移除这个符号连接时,方法有三种: + +1. 直接到 `/etc/rc2.d` 下删掉相应的链接,当然不是最好的方法; + +2. 推荐做法:`update-rc.d -f s10 remove` +3. 如果 update-rc.d 命令你不熟悉,还可以试试看 rcconf 这个命令,也很方便。 + +> :point_right: 参考: +> +> - [linux 添加开机自启动脚本示例详解](https://blog.csdn.net/linuxshine/article/details/50717272) +> - [linux 设置开机自启动](https://www.cnblogs.com/ssooking/p/6094740.html) + +### 定时执行脚本 + +## 配置 + +### 设置 Linux 启动模式 + +1. 停机(记得不要把 initdefault 配置为 0,因为这样会使 Linux 不能启动) +2. 单用户模式,就像 Win9X 下的安全模式 +3. 多用户,但是没有 NFS +4. 完全多用户模式,准则的运行级 +5. 通常不用,在一些特殊情况下可以用它来做一些事情 +6. X11,即进到 X-Window 系统 +7. 重新启动 (记得不要把 initdefault 配置为 6,因为这样会使 Linux 不断地重新启动) + +设置方法: + +```shell +sed -i 's/id:5:initdefault:/id:3:initdefault:/' /etc/inittab +``` + +## 参考资料 + +- [CentOS7 使用 firewalld 打开关闭防火墙与端口](https://www.cnblogs.com/moxiaoan/p/5683743.html) +- [linux 定时执行脚本](https://blog.csdn.net/z_yong_cool/article/details/79288397) \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/02.samba.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/02.samba.md" new file mode 100644 index 0000000000..02e6d62c0d --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/02.samba.md" @@ -0,0 +1,461 @@ +--- +title: Samba 应用 +date: 2018-03-01 15:29:00 +order: 02 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - Samba +permalink: /pages/77993f/ +--- + +# Samba 应用 + +> samba 是在 Linux 和 UNIX 系统上实现 SMB 协议的一个免费软件。 +> +> samba 提供了在不同计算机(即使操作系统不同)上共享服务的能力。 +> +> 关键词:`samba`, `selinux` + +## 安装配置 samba + +本文将以一个完整的示例来展示如何配置 samba 来实现 Linux 和 Windows 的文件共享。 + +目标:假设希望共享 Linux 服务器上的 /share/fs 目录。 + +### 查看是否已经安装 samba + +- CentOS:`rpm -qa | grep samba` +- Ubuntu:`dpkg -l | grep samba` + +### 安装 samba 工具 + +- CentOS:`yum install -y samba samba-client samba-common` +- Ubuntu:`sudo apt-get install -y samba samba-client` + +### 配置 samba + +samba 服务的配置文件是 `/etc/samba/smb.conf`,如果没有则 samba 无法启动。 + +执行以下命令,编辑配置文件: + +```bash +vim /etc/samba/smb.conf +``` + +修改配置如下: + +```ini +[global] + workgroup = SAMBA + security = user + + passdb backend = tdbsam + + printing = cups + printcap name = cups + load printers = yes + cups options = raw + +[homes] + comment = Home Directories + valid users = %S, %D%w%S + browseable = No + read only = No + inherit acls = Yes + +[printers] + comment = All Printers + path = /var/tmp + printable = Yes + create mask = 0600 + browseable = No + +[print$] + comment = Printer Drivers + path = /var/lib/samba/drivers + write list = @printadmin root + force group = @printadmin + create mask = 0664 + directory mask = 0775 + +[fs] + comment = share folder + path = /share/fs + browseable = yes + writable = yes + read only = no + guest ok = yes + create mask = 0777 + directory mask = 0777 + public = yes + valid users = root +``` + +> 说明: +> +> - 我在这里添加了一个 **[fs]** 标签,这就是共享区域的配置。 +> - 这里设置 `path` 属性为 `/share/fs`,意味着准备共享 `/share/fs` 目录,需要根据实际需要设置路径。`/share/fs` 目录的权限要设置为 **777**:`chmod 777 /share/fs`。 +> - `browseable`、`writable` 等属性就比较容易理解了,即配置共享目录的访问权限。 +> - `valid users` 属性指定允许访问的用户,需要注意的是指定的用户必须是 Linux 机器上实际存在的用户。 + +### 创建 samba 用户 + +创建的 samba 用户必须是 Linux 机器上实际存在的用户。 + +```bash +$ sudo smbpasswd -a root +New SMB password: +Retype new SMB password: +Added user root. +``` + +根据提示输入 samba 用户的密码。当 samba 服务成功安装、启动后,通过 Windows 系统访问机器共享目录时,就要输入这里配置的用户名、密码。 + +- 查看 samba 服务器中已拥有哪些用户 - `pdbedit -L` +- 删除 samba 服务中的某个用户 - `smbpasswd -x 用户名` + +### 启动 samba 服务 + +CentOS 6 + +```bash +$ sudo service samba restart # 重启 samba +$ sudo service smb restart # 重启 samba +``` + +CentOS 7 + +```bash +$ sudo systemctl start smb.service # 启动 samba +$ sudo systemctl restart smb.service # 重启 samba +$ sudo systemctl enable smb.service # 设置开机自动启动 +$ sudo systemctl status smb.service # 查询 samba 状态 +``` + +Ubuntu 16.04.3 + +``` +$ sudo service smbd restart +``` + +### 为 samba 添加防火墙规则 + +``` +$ sudo firewall-cmd --permanent --zone=public --add-service=samba +$ sudo firewall-cmd --reload +``` + +### 测试 samba 服务 + +``` +$ smbclient //localhost/fs -U root +``` + +输入 samba 用户的密码,如果成功,就会进入 `smb: \>`。 + +### 访问 samba 服务共享的目录 + +Windows: + +访问:`\\<你的ip>\<你的共享路径>` : + +![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20180920180928161334.png) + +Mac: + +与 Windows 类似,直接在 Finder 中访问 `smb://<你的ip>/<你的共享路径>` 即可。 + +## 配置详解 + +### samba 默认配置 + +你可以从 [这里](https://git.samba.org/samba.git/?p=samba.git;a=blob_plain;f=examples/smb.conf.default;hb=HEAD) 获取到默认配置文件: + +``` +$ cp /etc/samba/smb.conf /etc/samba/smb.conf.bak +$ wget "https://git.samba.org/samba.git/?p=samba.git;a=blob_plain;f=examples/smb.conf.default;hb=HEAD" -O /etc/samba/smb.conf +``` + +smb.conf 默认内容如下: + +```ini +[global] + workgroup = SAMBA + security = user + + passdb backend = tdbsam + + printing = cups + printcap name = cups + load printers = yes + cups options = raw + +[homes] + comment = Home Directories + valid users = %S, %D%w%S + browseable = No + read only = No + inherit acls = Yes + +[printers] + comment = All Printers + path = /var/tmp + printable = Yes + create mask = 0600 + browseable = No + +[print$] + comment = Printer Drivers + path = /var/lib/samba/drivers + write list = root + create mask = 0664 + directory mask = 0775 +``` + +### 全局参数 [global] + +```ini +[global] + +config file = /usr/local/samba/lib/smb.conf.%m +说明:config file可以让你使用另一个配置文件来覆盖缺省的配置文件。如果文件 不存在,则该项无效。这个参数很有用,可以使得samba配置更灵活,可以让一台samba服务器模拟多台不同配置的服务器。比如,你想让PC1(主机名)这台电脑在访问Samba Server时使用它自己的配置文件,那么先在/etc/samba/host/下为PC1配置一个名为smb.conf.pc1的文件,然后在smb.conf中加入:config file=/etc/samba/host/smb.conf.%m。这样当PC1请求连接Samba Server时,smb.conf.%m就被替换成smb.conf.pc1。这样,对于PC1来说,它所使用的Samba服务就是由smb.conf.pc1定义的,而其他机器访问Samba Server则还是应用smb.conf。 + +workgroup = WORKGROUP +说明:设定 Samba Server 所要加入的工作组或者域。 + +server string = Samba Server Version %v +说明:设定 Samba Server 的注释,可以是任何字符串,也可以不填。宏%v表示显示Samba的版本号。 + +netbios name = smbserver +说明:设置Samba Server的NetBIOS名称。如果不填,则默认会使用该服务器的DNS名称的第一部分。netbios name和workgroup名字不要设置成一样了。 + +interfaces = lo eth0 192.168.12.2/24 192.168.13.2/24 +说明:设置Samba Server监听哪些网卡,可以写网卡名,也可以写该网卡的IP地址。 + +hosts allow = 127.192.168.1 192.168.10.1 +说明:表示允许连接到Samba Server的客户端,多个参数以空格隔开。可以用一个IP表示,也可以用一个网段表示。hosts deny 与hosts allow 刚好相反。 +例如: +# 表示容许来自172.17.2.*.*的主机连接,但排除172.17.2.50 +hosts allow=172.17.2.EXCEPT172.17.2.50 +# 表示容许来自172.17.2.0/255.255.0.0子网中的所有主机连接 +hosts allow=172.17.2.0/255.255.0.0 +# 表示容许来自M1和M2两台计算机连接 +hosts allow=M1,M2 +# 表示容许来自SC域的所有计算机连接 +hosts allow=@SC +max connections = 0 +说明:max connections用来指定连接Samba Server的最大连接数目。如果超出连接数目,则新的连接请求将被拒绝。0表示不限制。 + +deadtime = 0 +说明:deadtime用来设置断掉一个没有打开任何文件的连接的时间。单位是分钟,0代表Samba Server不自动切断任何连接。 + +time server = yes/no +说明:time server用来设置让nmdb成为windows客户端的时间服务器。 + +log file = /var/log/samba/log.%m +说明:设置Samba Server日志文件的存储位置以及日志文件名称。在文件名后加个宏%m(主机名),表示对每台访问Samba Server的机器都单独记录一个日志文件。如果pc1、pc2访问过Samba Server,就会在/var/log/samba目录下留下log.pc1和log.pc2两个日志文件。 + +max log size = 50 +说明:设置Samba Server日志文件的最大容量,单位为kB,0代表不限制。 + +security = user +说明:设置用户访问Samba Server的验证方式,一共有四种验证方式。 +1. share:用户访问Samba Server不需要提供用户名和口令, 安全性能较低。 +2. user:Samba Server共享目录只能被授权的用户访问,由Samba Server负责检查账号和密码的正确性。账号和密码要在本Samba Server中建立。 +3. server:依靠其他Windows NT/2000或Samba Server来验证用户的账号和密码,是一种代理验证。此种安全模式下,系统管理员可以把所有的Windows用户和口令集中到一个NT系统上,使用Windows NT进行Samba认证, 远程服务器可以自动认证全部用户和口令,如果认证失败,Samba将使用用户级安全模式作为替代的方式。 +4. domain:域安全级别,使用主域控制器(PDC)来完成认证。 + +passdb backend = tdbsam +说明:passdb backend就是用户后台的意思。目前有三种后台:smbpasswd、tdbsam和ldapsam。sam应该是security account manager(安全账户管理)的简写。 + +smbpasswd:该方式是使用smb自己的工具smbpasswd来给系统用户(真实 +用户或者虚拟用户)设置一个Samba密码,客户端就用这个密码来访问Samba的资源。 +1. smbpasswd文件默认在/etc/samba目录下,不过有时候要手工建立该文件。 +2. tdbsam:该方式则是使用一个数据库文件来建立用户数据库。数据库文件叫passdb.tdb,默认在/etc/samba目录下。passdb.tdb用户数据库可以使用smbpasswd –a来建立Samba用户,不过要建立的Samba用户必须先是系统用户。我们也可以使用pdbedit命令来建立Samba账户。pdbedit命令的参数很多,我们列出几个主要的。 + pdbedit –a username:新建Samba账户。 + pdbedit –x username:删除Samba账户。 + pdbedit –L:列出Samba用户列表,读取passdb.tdb数据库文件。 + pdbedit –Lv:列出Samba用户列表的详细信息。 + pdbedit –c “[D]” –u username:暂停该Samba用户的账号。 + pdbedit –c “[]” –u username:恢复该Samba用户的账号。 +3. ldapsam:该方式则是基于LDAP的账户管理方式来验证用户。首先要建立LDAP服务,然后设置“passdb backend = ldapsam:ldap://LDAP Server” + +encrypt passwords = yes/no +说明:是否将认证密码加密。因为现在windows操作系统都是使用加密密码,所以一般要开启此项。不过配置文件默认已开启。 + +smb passwd file = /etc/samba/smbpasswd +说明:用来定义samba用户的密码文件。smbpasswd文件如果没有那就要手工新建。 + +username map = /etc/samba/smbusers +说明:用来定义用户名映射,比如可以将root换成administrator、admin等。不过要事先在smbusers文件中定义好。比如:root = administrator admin,这样就可以用administrator或admin这两个用户来代替root登陆Samba Server,更贴近windows用户的习惯。 + +guest account = nobody +说明:用来设置guest用户名。 + +socket options = TCP_NODELAY SO_RCVBUF=8192 SO_SNDBUF=8192 +说明:用来设置服务器和客户端之间会话的Socket选项,可以优化传输速度。 + +domain master = yes/no +说明:设置Samba服务器是否要成为网域主浏览器,网域主浏览器可以管理跨子网域的浏览服务。 + +local master = yes/no +说明:local master用来指定Samba Server是否试图成为本地网域主浏览器。如果设为no,则永远不会成为本地网域主浏览器。但是即使设置为yes,也不等于该Samba Server就能成为主浏览器,还需要参加选举。 + +preferred master = yes/no +说明:设置Samba Server一开机就强迫进行主浏览器选举,可以提高Samba Server成为本地网域主浏览器的机会。如果该参数指定为yes时,最好把domain master也指定为yes。使用该参数时要注意:如果在本Samba Server所在的子网有其他的机器(不论是windows NT还是其他Samba Server)也指定为首要主浏览器时,那么这些机器将会因为争夺主浏览器而在网络上大发广播,影响网络性能。如果同一个区域内有多台Samba Server,将上面三个参数设定在一台即可。 + +os level = 200 +说明:设置samba服务器的os level。该参数决定Samba Server是否有机会成为本地网域的主浏览器。os level从0到255,winNT的os level是32,win95/98的os level是1。Windows 2000的os level是64。如果设置为0,则意味着Samba Server将失去浏览选择。如果想让Samba Server成为PDC,那么将它的os level值设大些。 + +domain logons = yes/no +说明:设置Samba Server是否要做为本地域控制器。主域控制器和备份域控制器都需要开启此项。 + +logon . = %u.bat +说明:当使用者用windows客户端登陆,那么Samba将提供一个登陆档。如果设置成%u.bat,那么就要为每个用户提供一个登陆档。如果人比较多,那就比较麻烦。可以设置成一个具体的文件名,比如start.bat,那么用户登陆后都会去执行start.bat,而不用为每个用户设定一个登陆档了。这个文件要放置在[netlogon]的path设置的目录路径下。 + +wins support = yes/no +说明:设置samba服务器是否提供wins服务。 + +wins server = wins服务器IP地址 +说明:设置Samba Server是否使用别的wins服务器提供wins服务。 + +wins proxy = yes/no +说明:设置Samba Server是否开启wins代理服务。 + +dns proxy = yes/no +说明:设置Samba Server是否开启dns代理服务。 + +load printers = yes/no +说明:设置是否在启动Samba时就共享打印机。 + +printcap name = cups +说明:设置共享打印机的配置文件。 + +printing = cups +说明:设置Samba共享打印机的类型。现在支持的打印系统有:bsd, sysv, plp, lprng, aix, hpux, qnx +``` + +### 共享参数 [共享名] + +```ini +[共享名] + +comment = 任意字符串 +说明:comment是对该共享的描述,可以是任意字符串。 + +path = 共享目录路径 +说明:path用来指定共享目录的路径。可以用%u、%m这样的宏来代替路径里的unix用户和客户机的Netbios名,用宏表示主要用于[homes]共享域。例如:如果我们不打算用home段做为客户的共享,而是在/home/share/下为每个Linux用户以他的用户名建个目录,作为他的共享目录,这样path就可以写成:path = /home/share/%u; 。用户在连接到这共享时具体的路径会被他的用户名代替,要注意这个用户名路径一定要存在,否则,客户机在访问时会找不到网络路径。同样,如果我们不是以用户来划分目录,而是以客户机来划分目录,为网络上每台可以访问samba的机器都各自建个以它的netbios名的路径,作为不同机器的共享资源,就可以这样写:path = /home/share/%m 。 + +browseable = yes/no +说明:browseable用来指定该共享是否可以浏览。 + +writable = yes/no +说明:writable用来指定该共享路径是否可写。 + +available = yes/no +说明:available用来指定该共享资源是否可用。 + +admin users = 该共享的管理者 +说明:admin users用来指定该共享的管理员(对该共享具有完全控制权限)。在samba 3.0中,如果用户验证方式设置成“security=share”时,此项无效。 +例如:admin users =bobyuan,jane(多个用户中间用逗号隔开)。 + +valid users = 允许访问该共享的用户 +说明:valid users用来指定允许访问该共享资源的用户。 +例如:valid users = bobyuan,@bob,@tech(多个用户或者组中间用逗号隔开,如果要加入一个组就用“@+组名”表示。) + +invalid users = 禁止访问该共享的用户 +说明:invalid users用来指定不允许访问该共享资源的用户。 +例如:invalid users = root,@bob(多个用户或者组中间用逗号隔开。) + +write list = 允许写入该共享的用户 +说明:write list用来指定可以在该共享下写入文件的用户。 +例如:write list = bobyuan,@bob + +public = yes/no +说明:public用来指定该共享是否允许guest账户访问。 + +guest ok = yes/no +说明:意义同“public”。 + +几个特殊共享: +[homes] +comment = Home Directories +browseable = no +writable = yes +valid users = %S +; valid users = MYDOMAIN\%S + +[printers] +comment = All Printers +path = /var/spool/samba +browseable = no +guest ok = no +writable = no +printable = yes + +[netlogon] +comment = Network Logon Service +path = /var/lib/samba/netlogon +guest ok = yes +writable = no +share modes = no + +[Profiles] +path = /var/lib/samba/profiles +browseable = no +guest ok = yes +``` + +## 常见问题 + +### 你可能没有权限访问网络资源 + +问题现象: + +- 出现 **NT_STATUS_ACCESS_DENIED** 错误 +- Windows 下成功登陆 samba 后,点击共享目录仍然提示——你可能没有权限访问网络资源。 + +解决步骤: + +1. 检查是否配置了防火墙规则 + +```bash +# 一种方法是强行关闭防火墙 +$ sudo service iptables stop + +# 另一种方法是配置防火墙规则 +$ sudo firewall-cmd --permanent --zone=public --add-service=samba +$ sudo firewall-cmd --reload +``` + +2. 关闭 selinux + +```bash +# 将 /etc/selinux/config 文件中的 SELINUX 设为 disabled +$ sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config + +# 重启生效 +$ reboot +``` + +### window 下对 samba 的清理操作 + +1. windows 清除访问 samba 局域网密码缓存 + - 在 dos 窗口中输入 `control userpasswords2` 或者 `control keymgr.dll`,然后【高级】/【密码管理】,删掉保存的该机器密码。 +2. windows 清除连接的 linux 的 samba 服务缓存 + 1. 打开 win 的命令行。 + 2. 输入 net use,就会打印出当前缓存的连接上列表。 + 3. 根据列表,一个个删除连接: net use 远程连接名称 /del;或者一次性全部删除:`net use * /del`。 + +## 参考资料 + +- http://blog.51cto.com/yuanbin/115761 +- https://www.jianshu.com/p/750be209a6f0 +- https://github.com/judasn/Linux-Tutorial/blob/master/markdown-file/Samba.md +- https://blog.csdn.net/lan120576664/article/details/50396511 \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/03.ntp.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/03.ntp.md" new file mode 100644 index 0000000000..92a0bca54e --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/03.ntp.md" @@ -0,0 +1,202 @@ +--- +title: 时间服务器 - NTP +date: 2020-02-11 13:11:00 +order: 03 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - NTP +permalink: /pages/f50fdb/ +--- + +# 时间服务器 - NTP + +## NTP 简介 + +网络时间协议(英语:Network Time Protocol,缩写:NTP)是在数据网络潜伏时间可变的计算机系统之间通过分组交换进行时钟同步的一个网络协议,位于 OSI 模型的应用层。自 1985 年以来,NTP 是目前仍在使用的最古老的互联网协议之一。NTP 由特拉华大学的 David L. Mills(英语:David L. Mills)设计。 + +**NTP 意图将所有参与计算机的协调世界时(UTC)时间同步到几毫秒的误差内**。 + +NTP 要点: + +- 地球共有 24 个时区,而以格林威治时间 (GMT) 为标准时间; +- 中国本地时间为 GMT +8 小时; +- 最准确的时间为使用原子钟 (Atomic clock) 所计算的,例如 UTC (Coordinated Universal Time) 就是一例; +- Linux 系统本来就有两种时间,一种是 Linux 以 `1970/01/01` 开始计数的系统时间,一种则是 BIOS 记载的硬件时间; +- Linux 可以透过网络校时,最常见的网络校时为使用 NTP 服务器,这个服务启动在 `udp port 123`; +- 时区档案主要放置于 `/usr/share/zoneinfo/` 目录下,而本地时区则参考 `/etc/localtime`; +- NTP 服务器为一种阶层式的服务,所以 NTP 服务器本来就会与上层时间服务器作时间的同步化, 因此 `nptd` 与 `ntpdate` 两个指令不可同时使用; +- NTP 服务器的联机状态可以使用 `ntpstat` 及 `ntpq -p` 来查询; +- NTP 提供的客户端软件为 `ntpdate` 这个指令; +- 在 Linux 下想要手动处理时间时,需以 `date` 设定时间后,以 `hwclock -w` 来写入 BIOS 所记录的时间。 +- NTP 服务器之间的时间误差不可超过 1000 秒,否则 NTP 服务会自动关闭。 + +> 更多 NTP 详情可以参考:[鸟哥的 Linux 私房菜-- NTP 时间服务器](http://cn.linux.vbird.org/linux_server/0440ntp.php) + +## ntpd 服务 + +> 环境:CentOS + +### yum 安装 + +CentOS 安装 NTP 很简单,执行以下命令即可: + +```shell +yum -y install ntp +``` + +### ntpd 配置 + +ntp 的配置文件路径为: `/etc/ntp.conf` ,参考配置: + +```shell +# 1. 先处理权限方面的问题,包括放行上层服务器以及开放区网用户来源: +# restrict default kod nomodify notrap nopeer noquery # 拒绝 IPv4 的用户 +# restrict -6 default kod nomodify notrap nopeer noquery # 拒绝 IPv6 的用户 +restrict default nomodify notrap nopeer noquery +#restrict 192.168.100.0 mask 255.255.255.0 nomodify # 放行同局域网来源(根据网关和子网掩码决定) +restrict 127.0.0.1 # 默认值,放行本机 IPv4 来源 +restrict ::1 # 默认值,放行本机 IPv6 来源 + +# 2. 设定 NTP 主机来源 +# 注释掉默认 NTP 来源 +# server 0.centos.pool.ntp.org iburst +# server 1.centos.pool.ntp.org iburst +# server 2.centos.pool.ntp.org iburst +# server 3.centos.pool.ntp.org iburst +# 设置国内 NTP 来源 +server cn.pool.ntp.org prefer # 以这个主机为优先 +server ntp1.aliyun.com +server ntp.sjtu.edu.cn + +# 3. 预设时间差异分析档案与暂不用到的 keys 等,不需要更改它: +driftfile /var/lib/ntp/drift +keys /etc/ntp/keys +includefile /etc/ntp/crypto/pw +``` + +> 注意:如果更改配置,必须重启 NTP 服务(`systemctl restart ntpd`)才能生效。 + +### 放开防火墙限制 + +NTP 服务的端口是 `123`,使用的是 udp 协议,所以 NTP 服务器的防火墙必须对外开放 udp 123 这个端口。 + +如果防火墙使用 **`iptables`**,执行以下命令: + +```shell +iptables -A INPUT -p UDP -i eth0 -s 192.168.0.0/24 --dport 123 -j ACCEPT +``` + +如果防火墙使用 **`firewalld`**,执行以下命令: + +```shell +firewall-cmd --zone=public --add-port=123/udp --permanent +``` + +### ntpd 服务命令 + +```shell +systemctl enable ntpd.service # 开启服务(开机自动启动服务) +systemctl disable ntpd.service # 关闭服务(开机不会自动启动服务) +systemctl start ntpd.service # 启动服务 +systemctl stop ntpd.service # 停止服务 +systemctl restart ntpd.service # 重启服务 +systemctl reload ntpd.service # 重新载入配置 +systemctl status ntpd.service # 查看服务状态 +``` + +### 查看 ntp 服务状态 + +#### 验证 NTP 服务正常工作 + +执行 `ntpstat` 可以查看 ntp 服务器有无和上层 ntp 连通,,如果成功,可以看到类似以下的内容: + +```shell +$ ntpstat +synchronised to NTP server (5.79.108.34) at stratum 3 + time correct to within 1129 ms + polling server every 64 s +``` + +#### 查看 ntp 服务器与上层 ntp 的状态 + +```shell +ntpq -p + remote refid st t when poll reach delay offset jitter +============================================================================== +*ntp1.ams1.nl.le 130.133.1.10 2 u 36 64 367 230.801 5.271 2.791 + 120.25.115.20 10.137.53.7 2 u 33 64 377 25.930 15.908 3.168 + time.cloudflare 10.21.8.251 3 u 31 64 367 251.109 16.976 3.264 +``` + +## ntpdate 命令 + +> 注意:NTP 服务器为一种阶层式的服务,所以 NTP 服务器本来就会与上层时间服务器作时间的同步化, 因此 `nptd` 与 `ntpdate` 两个指令不可同时使用。 + +### 手动执行时间同步 + +`ntpdate` 命令是 NTP 的客户端软件,它可以用于请求时间同步。 + +语法: + +```shell +/usr/sbin/ntpdate +``` + +`ntp_server` 可以从 [国内 NTP 服务器](#国内 NTP 服务器) 中选择。 + +示例: + +```shell +$ ntpdate cn.pool.ntp.org +11 Feb 10:47:12 ntpdate[30423]: step time server 84.16.73.33 offset -49.894774 sec +``` + +### 自动定时同步时间 + +如果需要自动定时同步时间,可以利用 [Crontab](#crontab) 工具。本质就是用 crontab 定时执行一次手动时间同步命令 ntp。 + +示例:执行如下命令,就可以在每天凌晨 3 点同步系统时间: + +```shell +echo "0 3 * * * /usr/sbin/ntpdate cn.pool.ntp.org" >> /etc/crontab # 修改 crond 服务配置 +systemctl restart crond # 重启 crond 服务以生效 +``` + +## 四、国内 NTP 服务器 + +以下 NTP 服务器搜集自网络: + +```shell +cn.pool.ntp.org # 最常用的国内NTP服务器,参考:https://www.ntppool.org/zh/use.html +cn.ntp.org.cn # 中国 +edu.ntp.org.cn # 中国教育网 +ntp1.aliyun.com # 阿里云 +ntp2.aliyun.com # 阿里云 +ntp.sjtu.edu.cn # 上海交通大学 +s1a.time.edu.cn # 北京邮电大学 +s1b.time.edu.cn # 清华大学 +s1c.time.edu.cn # 北京大学 +s1d.time.edu.cn # 东南大学 +s1e.time.edu.cn # 清华大学 +s2a.time.edu.cn # 清华大学 +s2b.time.edu.cn # 清华大学 +s2c.time.edu.cn # 北京邮电大学 +s2d.time.edu.cn # 西南地区网络中心 +s2e.time.edu.cn # 西北地区网络中心 +s2f.time.edu.cn # 东北地区网络中心 +s2g.time.edu.cn # 华东南地区网络中心 +s2h.time.edu.cn # 四川大学网络管理中心 +s2j.time.edu.cn # 大连理工大学网络中心 +s2k.time.edu.cn # CERNET桂林主节点 +``` + +## 参考资料 + +- [鸟哥的 Linux 私房菜-- NTP 时间服务器](http://cn.linux.vbird.org/linux_server/0440ntp.php) +- [Linux 配置 ntp 时间服务器](https://www.cnblogs.com/quchunhui/p/7658853.html) \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/04.firewalld.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/04.firewalld.md" new file mode 100644 index 0000000000..3cb849c875 --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/04.firewalld.md" @@ -0,0 +1,54 @@ +--- +title: firewalld +date: 2020-02-11 13:11:00 +order: 04 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - 防火墙 +permalink: /pages/0cdbda/ +--- + +# 防火墙 - Firewalld + +## firewalld 服务命令 + +```shell +systemctl enable firewalld.service # 开启服务(开机自动启动服务) +systemctl disable firewalld.service # 关闭服务(开机不会自动启动服务) +systemctl start firewalld.service # 启动服务 +systemctl stop firewalld.service # 停止服务 +systemctl restart firewalld.service # 重启服务 +systemctl reload firewalld.service # 重新载入配置 +systemctl status firewalld.service # 查看服务状态 +``` + +## firewall-cmd 命令 + +`firewall-cmd` 命令用于配置防火墙。 + +```shell +firewall-cmd --version # 查看版本 +firewall-cmd --help # 查看帮助 +firewall-cmd --state # 显示状态 +firewall-cmd --reload # 更新防火墙规则 +firewall-cmd --get-active-zones # 查看区域信息 +firewall-cmd --get-zone-of-interface=eth0 # 查看指定接口所属区域 +firewall-cmd --panic-on # 拒绝所有包 +firewall-cmd --panic-off # 取消拒绝状态 +firewall-cmd --query-panic # 查看是否拒绝 + +firewall-cmd --zone=public --list-ports # 查看所有打开的端口 +firewall-cmd --zone=public --query-port=80/tcp # 查看是否有开放的 80 TCP 端口 +firewall-cmd --zone=public --add-port=8080/tcp --permanent # 添加开放端口(--permanent永久生效,没有此参数重启后失效) +firewall-cmd --zone=public --remove-port=80/tcp --permanent # 永久删除开放的 80 TCP 端口 +``` + +## 参考资料 + +- [CentOS7 使用 firewalld 打开关闭防火墙与端口](https://www.cnblogs.com/moxiaoan/p/5683743.html) \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/05.iptables.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/05.iptables.md" new file mode 100644 index 0000000000..a24a38767c --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/05.iptables.md" @@ -0,0 +1,295 @@ +--- +title: iptables +date: 2019-11-25 17:25:00 +order: 05 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - 防火墙 +permalink: /pages/eb9176/ +--- + +# Iptables 应用 + +> _iptables_ 是一个配置 Linux 内核 [防火墙](https://wiki.archlinux.org/index.php/Firewall) 的命令行工具,是 [netfilter](https://en.wikipedia.org/wiki/Netfilter) 项目的一部分。 可以直接配置,也可以通过许多前端和图形界面配置。 +> +> iptables 也经常代指该内核级防火墙。iptables 用于 [ipv4](https://en.wikipedia.org/wiki/Ipv4),_ip6tables_ 用于 [ipv6](https://en.wikipedia.org/wiki/Ipv6)。 +> +> [nftables](https://wiki.archlinux.org/index.php/Nftables) 已经包含在 [Linux kernel 3.13](http://www.phoronix.com/scan.php?page=news_item&px=MTQ5MDU) 中,以后会取代 iptables 成为主要的 Linux 防火墙工具。 +> +> 环境:CentOS7 + +## 简介 + +**iptables 可以检测、修改、转发、重定向和丢弃 IPv4 数据包**。 + +过滤 IPv4 数据包的代码已经内置于内核中,并且按照不同的目的被组织成 **表** 的集合。**表** 由一组预先定义的 **链** 组成,**链**包含遍历顺序规则。每一条规则包含一个谓词的潜在匹配和相应的动作(称为 **目标**),如果谓词为真,该动作会被执行。也就是说条件匹配。 + +## 安装 iptables + +(1)禁用 firewalld + +CentOS 7 上默认安装了 firewalld 作为防火墙,使用 iptables 建议关闭并禁用 firewalld。 + +```bash +systemctl stop firewalld +systemctl disable firewalld +``` + +(2)安装 iptables + +``` +yum install -y iptables-services +``` + +(3)服务管理 + +- 查看服务状态:`systemctl status iptables` +- 启用服务:`systemctl enable iptables` +- 禁用服务:`systemctl disable iptables` +- 启动服务:`systemctl start iptables` +- 重启服务:`systemctl restart iptables` +- 关闭服务: `systemctl stop iptables` + +## 命令 + +基本语法: + +``` +iptables(选项)(参数) +``` + +基本选项说明: + +| 参数 | 作用 | +| ----------- | ------------------------------------------------- | +| -P | 设置默认策略:iptables -P INPUT (DROP | +| -F | 清空规则链 | +| -L | 查看规则链 | +| -A | 在规则链的末尾加入新规则 | +| -I | num 在规则链的头部加入新规则 | +| -D | num 删除某一条规则 | +| -s | 匹配来源地址 IP/MASK,加叹号"!"表示除这个 IP 外。 | +| -d | 匹配目标地址 | +| -i | 网卡名称 匹配从这块网卡流入的数据 | +| -o | 网卡名称 匹配从这块网卡流出的数据 | +| -p | 匹配协议,如 tcp,udp,icmp | +| --dport num | 匹配目标端口号 | +| --sport num | 匹配来源端口号 | + +顺序: + +``` +iptables -t 表名 <-A/I/D/R> 规则链名 [规则号] <-i/o 网卡名> -p 协议名 <-s 源IP/源子网> --sport 源端口 <-d 目标IP/目标子网> --dport 目标端口 -j 动作 +``` + +## iptables 示例 + +### 清空当前的所有规则和计数 + +```shell +iptables -F # 清空所有的防火墙规则 +iptables -X # 删除用户自定义的空链 +iptables -Z # 清空计数 +``` + +### 配置允许 ssh 端口连接 + +```shell +iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 22 -j ACCEPT +# 22为你的ssh端口, -s 192.168.1.0/24表示允许这个网段的机器来连接,其它网段的ip地址是登陆不了你的机器的。 -j ACCEPT表示接受这样的请求 +``` + +### 允许本地回环地址可以正常使用 + +```shell +iptables -A INPUT -i lo -j ACCEPT +#本地圆环地址就是那个127.0.0.1,是本机上使用的,它进与出都设置为允许 +iptables -A OUTPUT -o lo -j ACCEPT +``` + +### 设置默认的规则 + +```shell +iptables -P INPUT DROP # 配置默认的不让进 +iptables -P FORWARD DROP # 默认的不允许转发 +iptables -P OUTPUT ACCEPT # 默认的可以出去 +``` + +### 配置白名单 + +```shell +iptables -A INPUT -p all -s 192.168.1.0/24 -j ACCEPT # 允许机房内网机器可以访问 +iptables -A INPUT -p all -s 192.168.140.0/24 -j ACCEPT # 允许机房内网机器可以访问 +iptables -A INPUT -p tcp -s 183.121.3.7 --dport 3380 -j ACCEPT # 允许183.121.3.7访问本机的3380端口 +``` + +### 开启相应的服务端口 + +```shell +iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 开启80端口,因为web对外都是这个端口 +iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT # 允许被ping +iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 已经建立的连接得让它进来 +``` + +### 保存规则到配置文件中 + +```shell +cp /etc/sysconfig/iptables /etc/sysconfig/iptables.bak # 任何改动之前先备份,请保持这一优秀的习惯 +iptables-save > /etc/sysconfig/iptables +cat /etc/sysconfig/iptables +``` + +### 列出已设置的规则 + +> iptables -L [-t 表名][链名] + +- 四个表名 `raw`,`nat`,`filter`,`mangle` +- 五个规则链名 `INPUT`、`OUTPUT`、`FORWARD`、`PREROUTING`、`POSTROUTING` +- filter 表包含`INPUT`、`OUTPUT`、`FORWARD`三个规则链 + +```shell +iptables -L -t nat # 列出 nat 上面的所有规则 +# ^ -t 参数指定,必须是 raw, nat,filter,mangle 中的一个 +iptables -L -t nat --line-numbers # 规则带编号 +iptables -L INPUT + +iptables -L -nv # 查看,这个列表看起来更详细 +``` + +### 清除已有规则 + +```shell +iptables -F INPUT # 清空指定链 INPUT 上面的所有规则 +iptables -X INPUT # 删除指定的链,这个链必须没有被其它任何规则引用,而且这条上必须没有任何规则。 + # 如果没有指定链名,则会删除该表中所有非内置的链。 +iptables -Z INPUT # 把指定链,或者表中的所有链上的所有计数器清零。 +``` + +### 删除已添加的规则 + +```shell +# 添加一条规则 +iptables -A INPUT -s 192.168.1.5 -j DROP +``` + +将所有 iptables 以序号标记显示,执行: + +```shell +iptables -L -n --line-numbers +``` + +比如要删除 INPUT 里序号为 8 的规则,执行: + +```shell +iptables -D INPUT 8 +``` + +### 开放指定的端口 + +```shell +iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT #允许本地回环接口(即运行本机访问本机) +iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT #允许已建立的或相关连的通行 +iptables -A OUTPUT -j ACCEPT #允许所有本机向外的访问 +iptables -A INPUT -p tcp --dport 22 -j ACCEPT #允许访问22端口 +iptables -A INPUT -p tcp --dport 80 -j ACCEPT #允许访问80端口 +iptables -A INPUT -p tcp --dport 21 -j ACCEPT #允许ftp服务的21端口 +iptables -A INPUT -p tcp --dport 20 -j ACCEPT #允许FTP服务的20端口 +iptables -A INPUT -j reject #禁止其他未允许的规则访问 +iptables -A FORWARD -j REJECT #禁止其他未允许的规则访问 +``` + +### 屏蔽 IP + +```shell +iptables -A INPUT -p tcp -m tcp -s 192.168.0.8 -j DROP # 屏蔽恶意主机(比如,192.168.0.8 +iptables -I INPUT -s 123.45.6.7 -j DROP #屏蔽单个IP的命令 +iptables -I INPUT -s 123.0.0.0/8 -j DROP #封整个段即从123.0.0.1到123.255.255.254的命令 +iptables -I INPUT -s 124.45.0.0/16 -j DROP #封IP段即从123.45.0.1到123.45.255.254的命令 +iptables -I INPUT -s 123.45.6.0/24 -j DROP #封IP段即从123.45.6.1到123.45.6.254的命令是 +``` + +### 指定数据包出去的网络接口 + +只对 OUTPUT,FORWARD,POSTROUTING 三个链起作用。 + +```shell +iptables -A FORWARD -o eth0 +``` + +### 查看已添加的规则 + +```shell +iptables -L -n -v +Chain INPUT (policy DROP 48106 packets, 2690K bytes) + pkts bytes target prot opt in out source destination + 5075 589K ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0 + 191K 90M ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 +1499K 133M ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 +4364K 6351M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED + 6256 327K ACCEPT icmp -- * * 0.0.0.0/0 0.0.0.0/0 + +Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + +Chain OUTPUT (policy ACCEPT 3382K packets, 1819M bytes) + pkts bytes target prot opt in out source destination + 5075 589K ACCEPT all -- * lo 0.0.0.0/0 0.0.0.0/0 +``` + +### 启动网络转发规则 + +公网`210.14.67.7`让内网`192.168.188.0/24`上网 + +```shell +iptables -t nat -A POSTROUTING -s 192.168.188.0/24 -j SNAT --to-source 210.14.67.127 +``` + +### 端口映射 + +本机的 2222 端口映射到内网 虚拟机的 22 端口 + +```shell +iptables -t nat -A PREROUTING -d 210.14.67.127 -p tcp --dport 2222 -j DNAT --to-dest 192.168.188.115:22 +``` + +### 字符串匹配 + +比如,我们要过滤所有 TCP 连接中的字符串`test`,一旦出现它我们就终止这个连接,我们可以这么做: + +```shell +iptables -A INPUT -p tcp -m string --algo kmp --string "test" -j REJECT --reject-with tcp-reset +iptables -L + +# Chain INPUT (policy ACCEPT) +# target prot opt source destination +# REJECT tcp -- anywhere anywhere STRING match "test" ALGO name kmp TO 65535 reject-with tcp-reset +# +# Chain FORWARD (policy ACCEPT) +# target prot opt source destination +# +# Chain OUTPUT (policy ACCEPT) +# target prot opt source destination +``` + +### 阻止 Windows 蠕虫的攻击 + +```shell +iptables -I INPUT -j DROP -p tcp -s 0.0.0.0/0 -m string --algo kmp --string "cmd.exe" +``` + +### 防止 SYN 洪水攻击 + +```shell +iptables -A INPUT -p tcp --syn -m limit --limit 5/second -j ACCEPT +``` + +## 参考资料 + +- https://wiki.archlinux.org/index.php/iptables_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87) +- https://wangchujiang.com/linux-command/c/iptables.html \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/06.crontab.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/06.crontab.md" new file mode 100644 index 0000000000..97db7b1e5c --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/06.crontab.md" @@ -0,0 +1,205 @@ +--- +title: crontab +date: 2020-02-11 13:11:00 +order: 06 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - cron +permalink: /pages/a6ec53/ +--- + +# 定时任务 - crontab + +> 环境:CentOS + +通过 `crontab` 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script 脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。 + +## crond 服务 + +Linux 通过 crond 服务来支持 crontab。 + +### 检查 `crond` 服务 + +使用 `systemctl list-unit-files` 命令确认 `crond` 服务是否已安装。 + +```shell +$ systemctl list-unit-files | grep crond +crond.service enabled +``` + +如果为 enabled,表示服务正运行。 + +### crond 服务命令 + +开机自动启动 crond 服务:`chkconfig crond on` + +或者,按以下命令手动启动: + +```shell +systemctl enable crond.service # 开启服务(开机自动启动服务) +systemctl disable crond.service # 关闭服务(开机不会自动启动服务) +systemctl start crond.service # 启动服务 +systemctl stop crond.service # 停止服务 +systemctl restart crond.service # 重启服务 +systemctl reload crond.service # 重新载入配置 +systemctl status crond.service # 查看服务状态 +``` + +## crontab + +### crontab 命令 + +crontab 命令格式如下: + +```shell +crontab [-u user] file crontab [-u user] [ -e | -l | -r ] +``` + +说明: + +- `-u user`:用来设定某个用户的 crontab 服务; +- `file`:file 是命令文件的名字,表示将 file 做为 crontab 的任务列表文件并载入 crontab。如果在命令行中没有指定这个文件,crontab 命令将接受标准输入(键盘)上键入的命令,并将它们载入 crontab。 +- `-e`:编辑某个用户的 crontab 文件内容。如果不指定用户,则表示编辑当前用户的 crontab 文件。 +- `-l`:显示某个用户的 crontab 文件内容,如果不指定用户,则表示显示当前用户的 crontab 文件内容。 +- `-r`:从/var/spool/cron 目录中删除某个用户的 crontab 文件,如果不指定用户,则默认删除当前用户的 crontab 文件。 +- `-i`:在删除用户的 crontab 文件时给确认提示。 + +有两种方法写入定时任务: + +- 在命令行输入:`crontab -e` 然后添加相应的任务,存盘退出。 +- 直接编辑 `/etc/crontab` 文件,即 `vi /etc/crontab`,添加相应的任务。 + +### crontab 文件 + +crontab 要执行的定时任务都被保存在 `/etc/crontab` 文件中。 + +crontab 的文件格式如下: + +![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200211113339.png) + +#### 标准字段 + +**逗号**用于分隔列表。例如,在第 5 个字段(星期几)中使用 `MON,WED,FRI` 表示周一、周三和周五。 + +**连字符**定义范围。例如,`2000-2010` 表示 2000 年至 2010 年期间的每年,包括 2000 年和 2010 年。 + +除非用反斜杠(\)转义,否则命令中的**百分号(%)**会被替换成换行符,第一个百分号后面的所有数据都会作为标准输入发送给命令。 + +| 字段 | 是否必填 | 允许值 | 允许特殊字符 | +| :----------- | :------- | :-------------- | :----------- | +| Minutes | 是 | 0–59 | `*`,`-` | +| Hours | 是 | 0–23 | `*`,`-` | +| Day of month | 是 | 1–31 | `*`,`-` | +| Month | 是 | 1–12 or JAN–DEC | `*`,`-` | +| Day of week | 是 | 0–6 or SUN–SAT | `*`,`-` | + +`/etc/crontab` 文件示例: + +```shell +SHELL=/bin/bash +PATH=/sbin:/bin:/usr/sbin:/usr/bin +MAILTO=root + +# For details see man 4 crontabs + +# Example of job definition: +# .---------------- minute (0 - 59) +# | .------------- hour (0 - 23) +# | | .---------- day of month (1 - 31) +# | | | .------- month (1 - 12) OR jan,feb,mar,apr ... +# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat +# | | | | | +# * * * * * user-name command to be executed + +# 每两个小时以root身份执行 /home/hello.sh 脚本 +0 */2 * * * root /home/hello.sh +``` + +### crontab 实例 + +#### 实例 1:每 1 分钟执行一次 myCommand + +```shell +* * * * * myCommand +``` + +#### 实例 2:每小时的第 3 和第 15 分钟执行 + +```shell +3,15 * * * * myCommand +``` + +#### 实例 3:在上午 8 点到 11 点的第 3 和第 15 分钟执行 + +```shell +3,15 8-11 * * * myCommand +``` + +#### 实例 4:每隔两天的上午 8 点到 11 点的第 3 和第 15 分钟执行 + +```shell +3,15 8-11 */2 * * myCommand +``` + +#### 实例 5:每周一上午 8 点到 11 点的第 3 和第 15 分钟执行 + +```shell +3,15 8-11 * * 1 myCommand +``` + +#### 实例 6:每晚的 21:30 重启 smb + +```shell +30 21 * * * /etc/init.d/smb restart +``` + +#### 实例 7:每月 1、10、22 日的 4 : 45 重启 smb + +```shell +45 4 1,10,22 * * /etc/init.d/smb restart +``` + +#### 实例 8:每周六、周日的 1 : 10 重启 smb + +```shell +10 1 * * 6,0 /etc/init.d/smb restart +``` + +#### 实例 9:每天 18 : 00 至 23 : 00 之间每隔 30 分钟重启 smb + +```shell +0,30 18-23 * * * /etc/init.d/smb restart +``` + +#### 实例 10:每星期六的晚上 11 : 00 pm 重启 smb + +```shell +0 23 * * 6 /etc/init.d/smb restart +``` + +#### 实例 11:每一小时重启 smb + +```shell +0 * * * * /etc/init.d/smb restart +``` + +#### 实例 12:晚上 11 点到早上 7 点之间,每隔一小时重启 smb + +```shell +0 23-7 * * * /etc/init.d/smb restart +``` + +## 参考资料 + +- **文章** + - [crontab 定时任务](https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/crontab.html) + - [linux 定时执行脚本](https://blog.csdn.net/z_yong_cool/article/details/79288397) +- **在线工具** + - [https://tool.lu/crontab/](https://tool.lu/crontab/) + - [https://cron.qqe2.com/](https://cron.qqe2.com/) \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/07.systemd.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/07.systemd.md" new file mode 100644 index 0000000000..80acb3eb66 --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/07.systemd.md" @@ -0,0 +1,981 @@ +--- +title: Systemd 应用 +date: 2019-10-08 21:28:00 +order: 07 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 +permalink: /pages/06c8d6/ +--- + +# Systemd 应用 + +> 搬运自:[Systemd 入门教程:命令篇](http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html)、[Systemd 入门教程:实战篇](hhttp://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html) + +Systemd 是 Linux 系统工具,用来启动[守护进程](http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html),已成为大多数发行版的标准配置。 + +本文介绍它的基本用法,分为上下两篇。今天介绍它的主要命令,[下一篇](http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html)介绍如何用于实战。 + +## 由来 + +历史上,[Linux 的启动](http://www.ruanyifeng.com/blog/2013/08/linux_boot_process.html)一直采用[`init`](https://en.wikipedia.org/wiki/Init)进程。 + +下面的命令用来启动服务。 + +```bash +$ sudo /etc/init.d/apache2 start +# 或者 +$ service apache2 start +``` + +这种方法有两个缺点。 + +一是启动时间长。`init`进程是串行启动,只有前一个进程启动完,才会启动下一个进程。 + +二是启动脚本复杂。`init`进程只是执行启动脚本,不管其他事情。脚本需要自己处理各种 +情况,这往往使得脚本变得很长。 + +## Systemd 概述 + +Systemd 就是为了解决这些问题而诞生的。它的设计目标是,为系统的启动和管理提供一套 +完整的解决方案。 + +根据 Linux 惯例,字母`d`是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就 +是它要守护整个系统。 + +使用了 Systemd,就不需要再用`init`了。Systemd 取代了`initd`,成为系统的第一个进 +程(PID 等于 1),其他进程都是它的子进程。 + +```bash +$ systemctl --version +``` + +上面的命令查看 Systemd 的版本。 + +Systemd 的优点是功能强大,使用方便,缺点是体系庞大,非常复杂。事实上,现在还有很 +多人反对使用 Systemd,理由就是它过于复杂,与操作系统的其他部分强耦合,违反"keep +simple, keep stupid" +的[Unix 哲学](http://www.ruanyifeng.com/blog/2009/06/unix_philosophy.html)。 + +![img](http://www.ruanyifeng.com/blogimg/asset/2016/bg2016030703.png) + +(上图为 Systemd 架构图) + +## 系统管理 + +Systemd 并不是一个命令,而是一组命令,涉及到系统管理的方方面面。 + +### systemctl + +`systemctl`是 Systemd 的主命令,用于管理系统。 + +```bash +# 重启系统 +$ sudo systemctl reboot + +# 关闭系统,切断电源 +$ sudo systemctl poweroff + +# CPU停止工作 +$ sudo systemctl halt + +# 暂停系统 +$ sudo systemctl suspend + +# 让系统进入冬眠状态 +$ sudo systemctl hibernate + +# 让系统进入交互式休眠状态 +$ sudo systemctl hybrid-sleep + +# 启动进入救援状态(单用户状态) +$ sudo systemctl rescue +``` + +### systemd-analyze + +`systemd-analyze`命令用于查看启动耗时。 + +```bash +# 查看启动耗时 +$ systemd-analyze + +# 查看每个服务的启动耗时 +$ systemd-analyze blame + +# 显示瀑布状的启动过程流 +$ systemd-analyze critical-chain + +# 显示指定服务的启动流 +$ systemd-analyze critical-chain atd.service +``` + +### hostnamectl + +`hostnamectl`命令用于查看当前主机的信息。 + +```bash +# 显示当前主机的信息 +$ hostnamectl + +# 设置主机名。 +$ sudo hostnamectl set-hostname rhel7 +``` + +### localectl + +`localectl`命令用于查看本地化设置。 + +```bash +# 查看本地化设置 +$ localectl + +# 设置本地化参数。 +$ sudo localectl set-locale LANG=en_GB.utf8 +$ sudo localectl set-keymap en_GB +``` + +### timedatectl + +`timedatectl`命令用于查看当前时区设置。 + +```bash +# 查看当前时区设置 +$ timedatectl + +# 显示所有可用的时区 +$ timedatectl list-timezones + +# 设置当前时区 +$ sudo timedatectl set-timezone America/New_York +$ sudo timedatectl set-time YYYY-MM-DD +$ sudo timedatectl set-time HH:MM:SS +``` + +### loginctl + +`loginctl`命令用于查看当前登录的用户。 + +```bash +# 列出当前session +$ loginctl list-sessions + +# 列出当前登录用户 +$ loginctl list-users + +# 列出显示指定用户的信息 +$ loginctl show-user ruanyf +``` + +## Unit + +### 含义 + +Systemd 可以管理所有系统资源。不同的资源统称为 Unit(单位)。 + +Unit 一共分成 12 种。 + +- Service unit:系统服务 +- Target unit:多个 Unit 构成的一个组 +- Device Unit:硬件设备 +- Mount Unit:文件系统的挂载点 +- Automount Unit:自动挂载点 +- Path Unit:文件或路径 +- Scope Unit:不是由 Systemd 启动的外部进程 +- Slice Unit:进程组 +- Snapshot Unit:Systemd 快照,可以切回某个快照 +- Socket Unit:进程间通信的 socket +- Swap Unit:swap 文件 +- Timer Unit:定时器 + +`systemctl list-units`命令可以查看当前系统的所有 Unit 。 + +```bash +# 列出正在运行的 Unit +$ systemctl list-units + +# 列出所有Unit,包括没有找到配置文件的或者启动失败的 +$ systemctl list-units --all + +# 列出所有没有运行的 Unit +$ systemctl list-units --all --state=inactive + +# 列出所有加载失败的 Unit +$ systemctl list-units --failed + +# 列出所有正在运行的、类型为 service 的 Unit +$ systemctl list-units --type=service +``` + +### Unit 的状态 + +`systemctl status`命令用于查看系统状态和单个 Unit 的状态。 + +```bash +# 显示系统状态 +$ systemctl status + +# 显示单个 Unit 的状态 +$ sysystemctl status bluetooth.service + +# 显示远程主机的某个 Unit 的状态 +$ systemctl -H root@rhel7.example.com status httpd.service +``` + +除了`status`命令,`systemctl`还提供了三个查询状态的简单方法,主要供脚本内部的判 +断语句使用。 + +```bash +# 显示某个 Unit 是否正在运行 +$ systemctl is-active application.service + +# 显示某个 Unit 是否处于启动失败状态 +$ systemctl is-failed application.service + +# 显示某个 Unit 服务是否建立了启动链接 +$ systemctl is-enabled application.service +``` + +### Unit 管理 + +对于用户来说,最常用的是下面这些命令,用于启动和停止 Unit(主要是 service)。 + +```bash +# 立即启动一个服务 +$ sudo systemctl start apache.service + +# 立即停止一个服务 +$ sudo systemctl stop apache.service + +# 重启一个服务 +$ sudo systemctl restart apache.service + +# 杀死一个服务的所有子进程 +$ sudo systemctl kill apache.service + +# 重新加载一个服务的配置文件 +$ sudo systemctl reload apache.service + +# 重载所有修改过的配置文件 +$ sudo systemctl daemon-reload + +# 显示某个 Unit 的所有底层参数 +$ systemctl show httpd.service + +# 显示某个 Unit 的指定属性的值 +$ systemctl show -p CPUShares httpd.service + +# 设置某个 Unit 的指定属性 +$ sudo systemctl set-property httpd.service CPUShares=500 +``` + +### 依赖关系 + +Unit 之间存在依赖关系:A 依赖于 B,就意味着 Systemd 在启动 A 的时候,同时会去启 +动 B。 + +`systemctl list-dependencies`命令列出一个 Unit 的所有依赖。 + +```bash +$ systemctl list-dependencies nginx.service +``` + +上面命令的输出结果之中,有些依赖是 Target 类型(详见下文),默认不会展开显示。如 +果要展开 Target,就需要使用`--all`参数。 + +```bash +$ systemctl list-dependencies --all nginx.service +``` + +## Unit 的配置文件 + +### 概述 + +每一个 Unit 都有一个配置文件,告诉 Systemd 怎么启动这个 Unit 。 + +Systemd 默认从目录`/etc/systemd/system/`读取配置文件。但是,里面存放的大部分文件 +都是符号链接,指向目录`/usr/lib/systemd/system/`,真正的配置文件存放在那个目录。 + +`systemctl enable`命令用于在上面两个目录之间,建立符号链接关系。 + +```bash +$ sudo systemctl enable clamd@scan.service +# 等同于 +$ sudo ln -s '/usr/lib/systemd/system/clamd@scan.service' '/etc/systemd/system/multi-user.target.wants/clamd@scan.service' +``` + +如果配置文件里面设置了开机启动,`systemctl enable`命令相当于激活开机启动。 + +与之对应的,`systemctl disable`命令用于在两个目录之间,撤销符号链接关系,相当于 +撤销开机启动。 + +```bash +$ sudo systemctl disable clamd@scan.service +``` + +配置文件的后缀名,就是该 Unit 的种类,比如`sshd.socket`。如果省略,Systemd 默认 +后缀名为`.service`,所以`sshd`会被理解成`sshd.service`。 + +### 配置文件的状态 + +`systemctl list-unit-files`命令用于列出所有配置文件。 + +```bash +# 列出所有配置文件 +$ systemctl list-unit-files + +# 列出指定类型的配置文件 +$ systemctl list-unit-files --type=service +``` + +这个命令会输出一个列表。 + +```bash +$ systemctl list-unit-files + +UNIT FILE STATE +chronyd.service enabled +clamd@.service static +clamd@scan.service disabled +``` + +这个列表显示每个配置文件的状态,一共有四种。 + +- enabled:已建立启动链接 +- disabled:没建立启动链接 +- static:该配置文件没有`[Install]`部分(无法执行),只能作为其他配置文件的依赖 +- masked:该配置文件被禁止建立启动链接 + +注意,从配置文件的状态无法看出,该 Unit 是否正在运行。这必须执行前面提到 +的`systemctl status`命令。 + +```bash +$ systemctl status bluetooth.service +``` + +一旦修改配置文件,就要让 SystemD 重新加载配置文件,然后重新启动,否则修改不会生 +效。 + +```bash +$ sudo systemctl daemon-reload +$ sudo systemctl restart httpd.service +``` + +### 配置文件的格式 + +配置文件就是普通的文本文件,可以用文本编辑器打开。 + +`systemctl cat`命令可以查看配置文件的内容。 + +```bash +$ systemctl cat atd.service + +[Unit] +Description=ATD daemon + +[Service] +Type=forking +ExecStart=/usr/bin/atd + +[Install] +WantedBy=multi-user.target +``` + +从上面的输出可以看到,配置文件分成几个区块。每个区块的第一行,是用方括号表示的区 +别名,比如`[Unit]`。注意,配置文件的区块名和字段名,都是大小写敏感的。 + +每个区块内部是一些等号连接的键值对。 + +```bash +[Section] +Directive1=value +Directive2=value + +. . . +``` + +注意,键值对的等号两侧不能有空格。 + +### 配置文件的区块 + +`[Unit]`区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 +Unit 的关系。它的主要字段如下。 + +- `Description`:简短描述 +- `Documentation`:文档地址 +- `Requires`:当前 Unit 依赖的其他 Unit,如果它们没有运行,当前 Unit 会启动失败 +- `Wants`:与当前 Unit 配合的其他 Unit,如果它们没有运行,当前 Unit 不会启动失败 +- `BindsTo`:与`Requires`类似,它指定的 Unit 如果退出,会导致当前 Unit 停止运行 +- `Before`:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之后启动 +- `After`:如果该字段指定的 Unit 也要启动,那么必须在当前 Unit 之前启动 +- `Conflicts`:这里指定的 Unit 不能与当前 Unit 同时运行 +- `Condition...`:当前 Unit 运行必须满足的条件,否则不会运行 +- `Assert...`:当前 Unit 运行必须满足的条件,否则会报启动失败 + +`[Install]`通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。它 +的主要字段如下。 + +- `WantedBy`:它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放 + 入`/etc/systemd/system`目录下面以 Target 名 + `.wants`后缀构成的子目录中 +- `RequiredBy`:它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放 + 入`/etc/systemd/system`目录下面以 Target 名 + `.required`后缀构成的子目录中 +- `Alias`:当前 Unit 可用于启动的别名 +- `Also`:当前 Unit 激活(enable)时,会被同时激活的其他 Unit + +`[Service]`区块用来 Service 的配置,只有 Service 类型的 Unit 才有这个区块。它的 +主要字段如下。 + +- `Type`:定义启动时的进程行为。它有以下几种值。 +- `Type=simple`:默认值,执行`ExecStart`指定的命令,启动主进程 +- `Type=forking`:以 fork 方式从父进程创建子进程,创建后父进程会立即退出 +- `Type=oneshot`:一次性进程,Systemd 会等当前服务退出,再继续往下执行 +- `Type=dbus`:当前服务通过 D-Bus 启动 +- `Type=notify`:当前服务启动完毕,会通知`Systemd`,再继续往下执行 +- `Type=idle`:若有其他任务执行完毕,当前服务才会运行 +- `ExecStart`:启动当前服务的命令 +- `ExecStartPre`:启动当前服务之前执行的命令 +- `ExecStartPost`:启动当前服务之后执行的命令 +- `ExecReload`:重启当前服务时执行的命令 +- `ExecStop`:停止当前服务时执行的命令 +- `ExecStopPost`:停止当其服务之后执行的命令 +- `RestartSec`:自动重启当前服务间隔的秒数 +- `Restart`:定义何种情况 Systemd 会自动重启当前服务,可能的值包括`always`(总是 + 重启)、`on-success`、`on-failure`、`on-abnormal`、`on-abort`、`on-watchdog` +- `TimeoutSec`:定义 Systemd 停止当前服务之前等待的秒数 +- `Environment`:指定环境变量 + +Unit 配置文件的完整字段清单,请参 +考[官方文档](https://www.freedesktop.org/software/systemd/man/systemd.unit.html)。 + +## Target + +启动计算机的时候,需要启动大量的 Unit。如果每一次启动,都要一一写明本次启动需要 +哪些 Unit,显然非常不方便。Systemd 的解决方案就是 Target。 + +简单说,Target 就是一个 Unit 组,包含许多相关的 Unit 。启动某个 Target 的时候 +,Systemd 就会启动里面所有的 Unit。从这个意义上说,Target 这个概念类似于"状态点 +",启动某个 Target 就好比启动到某种状态。 + +传统的`init`启动模式里面,有 RunLevel 的概念,跟 Target 的作用很类似。不同的是 +,RunLevel 是互斥的,不可能多个 RunLevel 同时启动,但是多个 Target 可以同时启动 +。 + +```bash +# 查看当前系统的所有 Target +$ systemctl list-unit-files --type=target + +# 查看一个 Target 包含的所有 Unit +$ systemctl list-dependencies multi-user.target + +# 查看启动时的默认 Target +$ systemctl get-default + +# 设置启动时的默认 Target +$ sudo systemctl set-default multi-user.target + +# 切换 Target 时,默认不关闭前一个 Target 启动的进程, +# systemctl isolate 命令改变这种行为, +# 关闭前一个 Target 里面所有不属于后一个 Target 的进程 +$ sudo systemctl isolate multi-user.target +``` + +Target 与 传统 RunLevel 的对应关系如下。 + +```bash +Traditional runlevel New target name Symbolically linked to... + +Runlevel 0 | runlevel0.target -> poweroff.target +Runlevel 1 | runlevel1.target -> rescue.target +Runlevel 2 | runlevel2.target -> multi-user.target +Runlevel 3 | runlevel3.target -> multi-user.target +Runlevel 4 | runlevel4.target -> multi-user.target +Runlevel 5 | runlevel5.target -> graphical.target +Runlevel 6 | runlevel6.target -> reboot.target +``` + +它与`init`进程的主要差别如下。 + +**(1)默认的 RunLevel**(在`/etc/inittab`文件设置)现在被默认的 Target 取代, +位置是`/etc/systemd/system/default.target`,通常符号链接到`graphical.target`( +图形界面)或者`multi-user.target`(多用户命令行)。 + +**(2)启动脚本的位置**,以前是`/etc/init.d`目录,符号链接到不同的 RunLevel 目 +录 (比如`/etc/rc3.d`、`/etc/rc5.d`等),现在则存放 +在`/lib/systemd/system`和`/etc/systemd/system`目录。 + +**(3)配置文件的位置**,以前`init`进程的配置文件是`/etc/inittab`,各种服务的 +配置文件存放在`/etc/sysconfig`目录。现在的配置文件主要存放在`/lib/systemd`目录 +,在`/etc/systemd`目录里面的修改可以覆盖原始设置。 + +## 日志管理 + +Systemd 统一管理所有 Unit 的启动日志。带来的好处就是,可以只用`journalctl`一个命 +令,查看所有日志(内核日志和应用日志)。日志的配置文件 +是`/etc/systemd/journald.conf`。 + +`journalctl`功能强大,用法非常多。 + +```bash +# 查看所有日志(默认情况下 ,只保存本次启动的日志) +$ sudo journalctl + +# 查看内核日志(不显示应用日志) +$ sudo journalctl -k + +# 查看系统本次启动的日志 +$ sudo journalctl -b +$ sudo journalctl -b -0 + +# 查看上一次启动的日志(需更改设置) +$ sudo journalctl -b -1 + +# 查看指定时间的日志 +$ sudo journalctl --since="2012-10-30 18:17:16" +$ sudo journalctl --since "20 min ago" +$ sudo journalctl --since yesterday +$ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00" +$ sudo journalctl --since 09:00 --until "1 hour ago" + +# 显示尾部的最新10行日志 +$ sudo journalctl -n + +# 显示尾部指定行数的日志 +$ sudo journalctl -n 20 + +# 实时滚动显示最新日志 +$ sudo journalctl -f + +# 查看指定服务的日志 +$ sudo journalctl /usr/lib/systemd/systemd + +# 查看指定进程的日志 +$ sudo journalctl _PID=1 + +# 查看某个路径的脚本的日志 +$ sudo journalctl /usr/bin/bash + +# 查看指定用户的日志 +$ sudo journalctl _UID=33 --since today + +# 查看某个 Unit 的日志 +$ sudo journalctl -u nginx.service +$ sudo journalctl -u nginx.service --since today + +# 实时滚动显示某个 Unit 的最新日志 +$ sudo journalctl -u nginx.service -f + +# 合并显示多个 Unit 的日志 +$ journalctl -u nginx.service -u php-fpm.service --since today + +# 查看指定优先级(及其以上级别)的日志,共有8级 +# 0: emerg +# 1: alert +# 2: crit +# 3: err +# 4: warning +# 5: notice +# 6: info +# 7: debug +$ sudo journalctl -p err -b + +# 日志默认分页输出,--no-pager 改为正常的标准输出 +$ sudo journalctl --no-pager + +# 以 JSON 格式(单行)输出 +$ sudo journalctl -b -u nginx.service -o json + +# 以 JSON 格式(多行)输出,可读性更好 +$ sudo journalctl -b -u nginx.serviceqq + -o json-pretty + +# 显示日志占据的硬盘空间 +$ sudo journalctl --disk-usage + +# 指定日志文件占据的最大空间 +$ sudo journalctl --vacuum-size=1G + +# 指定日志文件保存多久 +$ sudo journalctl --vacuum-time=1years +``` + +## 实战 + +### 开机启动 + +对于那些支持 Systemd 的软件,安装的时候,会自动在`/usr/lib/systemd/system`目录添 +加一个配置文件。 + +如果你想让该软件开机启动,就执行下面的命令(以`httpd.service`为例)。 + +```bash +$ sudo systemctl enable httpd +``` + +上面的命令相当于在`/etc/systemd/system`目录添加一个符号链接,指 +向`/usr/lib/systemd/system`里面的`httpd.service`文件。 + +这是因为开机时,`Systemd`只执行`/etc/systemd/system`目录里面的配置文件。这也意味 +着,如果把修改后的配置文件放在该目录,就可以达到覆盖原始配置的效果。 + +### 启动服务 + +设置开机启动以后,软件并不会立即启动,必须等到下一次开机。如果想现在就运行该软件 +,那么要执行`systemctl start`命令。 + +```bash +$ sudo systemctl start httpd +``` + +执行上面的命令以后,有可能启动失败,因此要用`systemctl status`命令查看一下该服务 +的状态。 + +```bash +$ sudo systemctl status httpd + +httpd.service - The Apache HTTP Server +Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled) +Active: active (running) since 金 2014-12-05 12:18:22 JST; 7min ago +Main PID: 4349 (httpd) +Status: "Total requests: 1; Current requests/sec: 0; Current traffic: 0 B/sec" +CGroup: /system.slice/httpd.service + ├─4349 /usr/sbin/httpd -DFOREGROUND + ├─4350 /usr/sbin/httpd -DFOREGROUND + ├─4351 /usr/sbin/httpd -DFOREGROUND + ├─4352 /usr/sbin/httpd -DFOREGROUND + ├─4353 /usr/sbin/httpd -DFOREGROUND + └─4354 /usr/sbin/httpd -DFOREGROUND + +12月 05 12:18:22 localhost.localdomain systemd[1]: Starting The Apache HTTP Server... +12月 05 12:18:22 localhost.localdomain systemd[1]: Started The Apache HTTP Server. +12月 05 12:22:40 localhost.localdomain systemd[1]: Started The Apache HTTP Server. +``` + +上面的输出结果含义如下。 + +- `Loaded`行:配置文件的位置,是否设为开机启动 +- `Active`行:表示正在运行 +- `Main PID`行:主进程 ID +- `Status`行:由应用本身(这里是 httpd )提供的软件当前状态 +- `CGroup`块:应用的所有子进程 +- 日志块:应用的日志 + +### 停止服务 + +终止正在运行的服务,需要执行`systemctl stop`命令。 + +```bash +$ sudo systemctl stop httpd.service +``` + +有时候,该命令可能没有响应,服务停不下来。这时候就不得不"杀进程"了,向正在运行的 +进程发出`kill`信号。 + +```bash +$ sudo systemctl kill httpd.service +``` + +此外,重启服务要执行`systemctl restart`命令。 + +```bash +$ sudo systemctl restart httpd.service +``` + +### 读懂配置文件 + +一个服务怎么启动,完全由它的配置文件决定。下面就来看,配置文件有些什么内容。 + +前面说过,配置文件主要放在`/usr/lib/systemd/system`目录,也可能 +在`/etc/systemd/system`目录。找到配置文件以后,使用文本编辑器打开即可。 + +`systemctl cat`命令可以用来查看配置文件,下面以`sshd.service`文件为例,它的作用 +是启动一个 SSH 服务器,供其他用户以 SSH 方式登录。 + +``` +$ systemctl cat sshd.service + +[Unit] +Description=OpenSSH server daemon +Documentation=man:sshd(8) man:sshd_config(5) +After=network.target sshd-keygen.service +Wants=sshd-keygen.service + +[Service] +EnvironmentFile=/etc/sysconfig/sshd +ExecStart=/usr/sbin/sshd -D $OPTIONS +ExecReload=/bin/kill -HUP $MAINPID +Type=simple +KillMode=process +Restart=on-failure +RestartSec=42s + +[Install] +WantedBy=multi-user.target +``` + +可以看到,配置文件分成几个区块,每个区块包含若干条键值对。 + +下面依次解释每个区块的内容。 + +### [Unit] 区块:启动顺序与依赖关系。 + +`Unit`区块的`Description`字段给出当前服务的简单描述,`Documentation`字段给出文档 +位置。 + +接下来的设置是启动顺序和依赖关系,这个比较重要。 + +> `After`字段:表示如果`network.target`或`sshd-keygen.service`需要启动,那 +> 么`sshd.service`应该在它们之后启动。 + +相应地,还有一个`Before`字段,定义`sshd.service`应该在哪些服务之前启动。 + +注意,`After`和`Before`字段只涉及启动顺序,不涉及依赖关系。 + +举例来说,某 Web 应用需要 postgresql 数据库储存数据。在配置文件中,它只定义要在 +postgresql 之后启动,而没有定义依赖 postgresql 。上线后,由于某种原因 +,postgresql 需要重新启动,在停止服务期间,该 Web 应用就会无法建立数据库连接。 + +设置依赖关系,需要使用`Wants`字段和`Requires`字段。 + +> `Wants`字段:表示`sshd.service`与`sshd-keygen.service`之间存在"弱依赖"关系,即 +> 如果"sshd-keygen.service"启动失败或停止运行,不影响`sshd.service`继续执行。 + +`Requires`字段则表示"强依赖"关系,即如果该服务启动失败或异常退出,那 +么`sshd.service`也必须退出。 + +注意,`Wants`字段与`Requires`字段只涉及依赖关系,与启动顺序无关,默认情况下是同 +时启动的。 + +### [Service] 区块:启动行为 + +`Service`区块定义如何启动当前服务。 + +#### 启动命令 + +许多软件都有自己的环境参数文件,该文件可以用`EnvironmentFile`字段读取。 + +> `EnvironmentFile`字段:指定当前服务的环境参数文件。该文件内部的`key=value`键值 +> 对,可以用`$key`的形式,在当前配置文件中获取。 + +上面的例子中,sshd 的环境参数文件是`/etc/sysconfig/sshd`。 + +配置文件里面最重要的字段是`ExecStart`。 + +> `ExecStart`字段:定义启动进程时执行的命令。 + +上面的例子中,启动`sshd`,执行的命令是`/usr/sbin/sshd -D $OPTIONS`,其中的变 +量`$OPTIONS`就来自`EnvironmentFile`字段指定的环境参数文件。 + +与之作用相似的,还有如下这些字段。 + +- `ExecReload`字段:重启服务时执行的命令 +- `ExecStop`字段:停止服务时执行的命令 +- `ExecStartPre`字段:启动服务之前执行的命令 +- `ExecStartPost`字段:启动服务之后执行的命令 +- `ExecStopPost`字段:停止服务之后执行的命令 + +请看下面的例子。 + +``` +[Service] +ExecStart=/bin/echo execstart1 +ExecStart= +ExecStart=/bin/echo execstart2 +ExecStartPost=/bin/echo post1 +ExecStartPost=/bin/echo post2 +``` + +上面这个配置文件,第二行`ExecStart`设为空值,等于取消了第一行的设置,运行结果如 +下。 + +``` +execstart2 +post1 +post2 +``` + +所有的启动设置之前,都可以加上一个连词号(`-`),表示"抑制错误",即发生错误的时 +候,不影响其他命令的执行。比如,`EnvironmentFile=-/etc/sysconfig/sshd`(注意等号 +后面的那个连词号),就表示即使`/etc/sysconfig/sshd`文件不存在,也不会抛出错误。 + +#### 启动类型 + +`Type`字段定义启动类型。它可以设置的值如下。 + +- simple(默认值):`ExecStart`字段启动的进程为主进程 +- forking:`ExecStart`字段将以`fork()`方式启动,此时父进程将会退出,子进程将成 + 为主进程 +- oneshot:类似于`simple`,但只执行一次,Systemd 会等它执行完,才启动其他服务 +- dbus:类似于`simple`,但会等待 D-Bus 信号后启动 +- notify:类似于`simple`,启动结束后会发出通知信号,然后 Systemd 再启动其他服 + 务 +- idle:类似于`simple`,但是要等到其他任务都执行完,才会启动该服务。一种使用场 + 合是为让该服务的输出,不与其他服务的输出相混合 + +下面是一个`oneshot`的例子,笔记本电脑启动时,要把触摸板关掉,配置文件可以这样写 +。 + +``` +[Unit] +Description=Switch-off Touchpad + +[Service] +Type=oneshot +ExecStart=/usr/bin/touchpad-off + +[Install] +WantedBy=multi-user.target +``` + +上面的配置文件,启动类型设为`oneshot`,就表明这个服务只要运行一次就够了,不需要 +长期运行。 + +如果关闭以后,将来某个时候还想打开,配置文件修改如下。 + +``` +[Unit] +Description=Switch-off Touchpad + +[Service] +Type=oneshot +ExecStart=/usr/bin/touchpad-off start +ExecStop=/usr/bin/touchpad-off stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +``` + +上面配置文件中,`RemainAfterExit`字段设为`yes`,表示进程退出以后,服务仍然保持执 +行。这样的话,一旦使用`systemctl stop`命令停止服务,`ExecStop`指定的命令就会执行 +,从而重新开启触摸板。 + +#### 重启行为 + +`Service`区块有一些字段,定义了重启行为。 + +> `KillMode`字段:定义 Systemd 如何停止 sshd 服务。 + +上面这个例子中,将`KillMode`设为`process`,表示只停止主进程,不停止任何 sshd 子 +进程,即子进程打开的 SSH session 仍然保持连接。这个设置不太常见,但对 sshd 很重 +要,否则你停止服务的时候,会连自己打开的 SSH session 一起杀掉。 + +`KillMode`字段可以设置的值如下。 + +- control-group(默认值):当前控制组里面的所有子进程,都会被杀掉 +- process:只杀主进程 +- mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号 +- none:没有进程会被杀掉,只是执行服务的 stop 命令。 + +接下来是`Restart`字段。 + +> `Restart`字段:定义了 sshd 退出后,Systemd 的重启方式。 + +上面的例子中,`Restart`设为`on-failure`,表示任何意外的失败,就将重启 sshd。如果 +sshd 正常停止(比如执行`systemctl stop`命令),它就不会重启。 + +`Restart`字段可以设置的值如下。 + +- no(默认值):退出后不会重启 +- on-success:只有正常退出时(退出状态码为 0),才会重启 +- on-failure:非正常退出时(退出状态码非 0),包括被信号终止和超时,才会重启 +- on-abnormal:只有被信号终止和超时,才会重启 +- on-abort:只有在收到没有捕捉到的信号终止时,才会重启 +- on-watchdog:超时退出,才会重启 +- always:不管是什么退出原因,总是重启 + +对于守护进程,推荐设为`on-failure`。对于那些允许发生错误退出的服务,可以设 +为`on-abnormal`。 + +最后是`RestartSec`字段。 + +> `RestartSec`字段:表示 Systemd 重启服务之前,需要等待的秒数。上面的例子设为等 +> 待 42 秒。 + +### [Install] 区块 + +`Install`区块,定义如何安装这个配置文件,即怎样做到开机启动。 + +`WantedBy`字段:表示该服务所在的 Target。 + +`Target`的含义是服务组,表示一组服务。`WantedBy=multi-user.target`指的是,sshd +所在的 Target 是`multi-user.target`。 + +这个设置非常重要,因为执行`systemctl enable sshd.service`命令时 +,`sshd.service`的一个符号链接,就会放在`/etc/systemd/system`目录下面 +的`multi-user.target.wants`子目录之中。 + +Systemd 有默认的启动 Target。 + +```bash +$ systemctl get-default +multi-user.target +``` + +上面的结果表示,默认的启动 Target 是`multi-user.target`。在这个组里的所有服务, +都将开机启动。这就是为什么`systemctl enable`命令能设置开机启动的原因。 + +使用 Target 的时候,`systemctl list-dependencies`命令和`systemctl isolate`命令也 +很有用。 + +```bash +# 查看 multi-user.target 包含的所有服务 +$ systemctl list-dependencies multi-user.target + +# 切换到另一个 target +# shutdown.target 就是关机状态 +$ sudo systemctl isolate shutdown.target +``` + +一般来说,常用的 Target 有两个:一个是`multi-user.target`,表示多用户命令行状态 +;另一个是`graphical.target`,表示图形用户状态,它依赖于`multi-user.target`。官 +方文档有一张非常清晰的 +[Target 依赖关系图](https://www.freedesktop.org/software/systemd/man/bootup.html#System%20Manager%20Bootup)。 + +### Target 的配置文件 + +Target 也有自己的配置文件。 + +```bash +$ systemctl cat multi-user.target + +[Unit] +Description=Multi-User System +Documentation=man:systemd.special(7) +Requires=basic.target +Conflicts=rescue.service rescue.target +After=basic.target rescue.service rescue.target +AllowIsolate=yes +``` + +注意,Target 配置文件里面没有启动命令。 + +上面输出结果中,主要字段含义如下。 + +- `Requires`字段:要求`basic.target`一起运行。 +- `Conflicts`字段:冲突字段。如果`rescue.service`或`rescue.target`正在运行 + ,`multi-user.target`就不能运行,反之亦然。 +- `After`:表示`multi-user.target`在`basic.target` 、 `rescue.service`、 + `rescue.target`之后启动,如果它们有启动的话。 +- `AllowIsolate`:允许使用`systemctl isolate`命令切换到`multi-user.target`。 + +### 修改配置文件后重启 + +修改配置文件以后,需要重新加载配置文件,然后重新启动相关服务。 + +```bash +# 重新加载配置文件 +$ sudo systemctl daemon-reload + +# 重启相关服务 +$ sudo systemctl restart foobar +``` + +## 参考资料 + +- [Systemd 入门教程:命令篇](http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html) +- [Systemd 入门教程:实战篇](hhttp://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html) \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/08.vim.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/08.vim.md" new file mode 100644 index 0000000000..6c5955ca95 --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/08.vim.md" @@ -0,0 +1,360 @@ +--- +title: vim +date: 2017-11-17 11:00:00 +order: 08 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - Vim +permalink: /pages/50ab65/ +--- + +# Vim 应用 + +## 概念 + +### 什么是 vim + +Vim 是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。和 Emacs 并列成为类 Unix 系统用户最喜欢的编辑器。 + +### Vim 的模式 + +基本上 vi/vim 共分为三种模式,分别是**命令模式(Command mode)**,**插入模式(Insert mode)**和**底线命令模式(Last line mode)**。 + +#### 命令模式 + +**用户刚刚启动 vi/vim,便进入了命令模式。** + +此状态下敲击键盘动作会被 Vim 识别为命令,而非输入字符。 + +#### 插入模式 + +**在命令模式下按下 `i` 就进入了输入模式。** + +在输入模式下,你可以输入文本内容。 + +#### 底线命令模式 + +**在命令模式下按下 `:`(英文冒号)就进入了底线命令模式。** + +底线命令模式可以输入单个或多个字符的命令,可用的命令非常多。 + +## Vim 渐进学习 + +### 存活 + +1. 安装 [vim](http://www.vim.org/) +2. 启动 vim +3. **什么也别干!**请先阅读 + +当你安装好一个编辑器后,你一定会想在其中输入点什么东西,然后看看这个编辑器是什么样子。但 vim 不是这样的,请按照下面的命令操作: + +- 启 动 Vim 后,vim 在 _Normal_ 模式下。 +- 让我们进入 _Insert_ 模式,请按下键 i 。(注:你会看到 vim 左下角有一个–insert–字样,表示,你可以以插入的方式输入了) +- 此时,你可以输入文本了,就像你用“记事本”一样。 +- 如果你想返回 _Normal_ 模式,请按 `ESC` 键。 + +现在,你知道如何在 _Insert_ 和 _Normal_ 模式下切换了。下面是一些命令,可以让你在 _Normal_ 模式下幸存下来: + +> - `i` → _Insert_ 模式,按 `ESC` 回到 _Normal_ 模式. +> - `x` → 删当前光标所在的一个字符。 +> - `:wq` → 存盘 + 退出 (`:w` 存盘, `:q` 退出) (注::w 后可以跟文件名) +> - `dd` → 删除当前行,并把删除的行存到剪贴板里 +> - `p` → 粘贴剪贴板 +> +> **推荐** +> +> - `hjkl` (强例推荐使用其移动光标,但不必需) → 你也可以使用光标键 (←↓↑→). 注: `j` 就像下箭头。 +> - `:help ` → 显示相关命令的帮助。你也可以就输入 `:help` 而不跟命令。(注:退出帮助需要输入:q) + +你能在 vim 幸存下来只需要上述的那 5 个命令,你就可以编辑文本了,你一定要把这些命令练成一种下意识的状态。于是你就可以开始进阶到第二级了。 + +当是,在你进入第二级时,需要再说一下 _Normal_ 模式。在一般的编辑器下,当你需要 copy 一段文字的时候,你需要使用 `Ctrl` 键,比如:`Ctrl-C`。也就是说,`Ctrl` 键就好像功能键一样,当你按下了功能键 `Ctrl` 后,C 就不在是 C 了,而且就是一个命令或是一个快键键了,**在 vim 的 Normal 模式下,所有的键都是功能键**。这个你需要知道。 + +> **标记** +> +> - 下面的文字中,如果是 `Ctrl-λ`我会写成 ``. +> - 以 `:` 开始的命令你需要输入 ``回车,例如 — 如果我写成 `:q` 也就是说你要输入 `:q`. + +### 感觉良好 + +上面的那些命令只能让你存活下来,现在是时候学习一些更多的命令了,下面是我的建议:(注:所有的命令都需要在 Normal 模式下使用,如果你不知道现在在什么样的模式,你就狂按几次 ESC 键) + +1. 各种插入模式 + + > - `a` → 在光标后插入 + > - `o` → 在当前行后插入一个新行 + > - `O` → 在当前行前插入一个新行 + > - `cw` → 替换从光标所在位置后到一个单词结尾的字符 + +2. 简单的移动光标 + + > - `0` → 数字零,到行头 + > - `^` → 到本行第一个不是 blank 字符的位置(所谓 blank 字符就是空格,tab,换行,回车等) + > - `$` → 到本行行尾 + > - `g_` → 到本行最后一个不是 blank 字符的位置。 + > - `/pattern` → 搜索 `pattern` 的字符串(注:如果搜索出多个匹配,可按 n 键到下一个) + +3. 拷贝/粘贴 + + (注:p/P 都可以,p 是表示在当前位置之后,P 表示在当前位置之前) + + > - `P` → 粘贴 + > - `yy` → 拷贝当前行当行于 `ddP` + +4. Undo/Redo + + > - `u` → undo + > - `` → redo + +5. 打开/保存/退出/改变文件 + + (Buffer) + + > - `:e ` → 打开一个文件 + > - `:w` → 存盘 + > - `:saveas ` → 另存为 `` + > - `:x`, `ZZ` 或 `:wq` → 保存并退出 (`:x` 表示仅在需要时保存,ZZ 不需要输入冒号并回车) + > - `:q!` → 退出不保存 `:qa!` 强行退出所有的正在编辑的文件,就算别的文件有更改。 + > - `:bn` 和 `:bp` → 你可以同时打开很多文件,使用这两个命令来切换下一个或上一个文件。(注:我喜欢使用:n 到下一个文件) + +花点时间熟悉一下上面的命令,一旦你掌握他们了,你就几乎可以干其它编辑器都能干的事了。但是到现在为止,你还是觉得使用 vim 还是有点笨拙,不过没关系,你可以进阶到第三级了。 + +### 更好,更强,更快 + +先恭喜你!你干的很不错。我们可以开始一些更为有趣的事了。在第三级,我们只谈那些和 vi 可以兼容的命令。 + +#### 更好 + +下面,让我们看一下 vim 是怎么重复自己的:1515G + +1. `.` → (小数点) 可以重复上一次的命令 +2. `N` → 重复某个命令 N 次 + +下面是一个示例,找开一个文件你可以试试下面的命令: + +> - `2dd` → 删除 2 行 +> - `3p` → 粘贴文本 3 次 +> - `100idesu [ESC]` → 会写下 “desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu “ +> - `.` → 重复上一个命令—— 100 “desu “. +> - `3.` → 重复 3 次 “desu” (注意:不是 300,你看,VIM 多聪明啊). + +#### 更强 + +你要让你的光标移动更有效率,你一定要了解下面的这些命令,**千万别跳过**。 + +1. N`G` → 到第 N 行 (注:注意命令中的 G 是大写的,另我一般使用 : N 到第 N 行,如 :137 到第 137 行) + +2. `gg` → 到第一行。(注:相当于 1G,或 :1) + +3. `G` → 到最后一行。 + +4. 按单词移动: + + > 1. `w` → 到下一个单词的开头。 + > 2. `e` → 到下一个单词的结尾。 + > + > \> 如果你认为单词是由默认方式,那么就用小写的 e 和 w。默认上来说,一个单词由字母,数字和下划线组成(注:程序变量) + > + > \> 如果你认为单词是由 blank 字符分隔符,那么你需要使用大写的 E 和 W。(注:程序语句) + > + > ![img](http://upload-images.jianshu.io/upload_images/3101171-46f752c581d79057.jpg) + +下面,让我来说说最强的光标移动: + +> - `%` : 匹配括号移动,包括 `(`, `{`, `[`. (注:你需要把光标先移到括号上) +> - `*` 和 `#`: 匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词(\*是下一个,#是上一个) + +相信我,上面这三个命令对程序员来说是相当强大的。 + +#### 更快 + +你一定要记住光标的移动,因为很多命令都可以和这些移动光标的命令连动。很多命令都可以如下来干: + +`` + +例如 `0y$` 命令意味着: + +- `0` → 先到行头 +- `y` → 从这里开始拷贝 +- `$` → 拷贝到本行最后一个字符 + +你可可以输入 `ye`,从当前位置拷贝到本单词的最后一个字符。 + +你也可以输入 `y2/foo` 来拷贝 2 个 “foo” 之间的字符串。 + +还有很多时间并不一定你就一定要按 y 才会拷贝,下面的命令也会被拷贝: + +- `d` (删除 ) +- `v` (可视化的选择) +- `gU` (变大写) +- `gu` (变小写) +- 等等 + +(注:可视化选择是一个很有意思的命令,你可以先按 v,然后移动光标,你就会看到文本被选择,然后,你可能 d,也可 y,也可以变大写等) + +### Vim 超能力 + +你只需要掌握前面的命令,你就可以很舒服的使用 VIM 了。但是,现在,我们向你介绍的是 VIM 杀手级的功能。下面这些功能是我只用 vim 的原因。 + +#### 在当前行上移动光标: `0` `^` `####`f`F`t`T`,``;` + +> - `0` → 到行头 +> - `^` → 到本行的第一个非 blank 字符 +> - `$` → 到行尾 +> - `g_` → 到本行最后一个不是 blank 字符的位置。 +> - `fa` → 到下一个为 a 的字符处,你也可以 fs 到下一个为 s 的字符。 +> - `t,` → 到逗号前的第一个字符。逗号可以变成其它字符。 +> - `3fa` → 在当前行查找第三个出现的 a。 +> - `F` 和 `T` → 和 `f` 和 `t` 一样,只不过是相反方向。 +> ![img](http://upload-images.jianshu.io/upload_images/3101171-00835b8316330c58.jpg) + +还有一个很有用的命令是 `dt"` → 删除所有的内容,直到遇到双引号—— `"。` + +#### 区域选择 `a` 或 `i` + +在 visual 模式下,这些命令很强大,其命令格式为 + +`a` 和 `i` + +- action 可以是任何的命令,如 `d` (删除), `y` (拷贝), `v` (可以视模式选择)。 +- object 可能是: `w` 一个单词, `W` 一个以空格为分隔的单词, `s` 一个句字, `p` 一个段落。也可以是一个特别的字符:`"、` `'、` `)、` `}、` `]。` + +假设你有一个字符串 `(map (+) ("foo"))`.而光标键在第一个 `o`的位置。 + +> - `vi"` → 会选择 `foo`. +> - `va"` → 会选择 `"foo"`. +> - `vi)` → 会选择 `"foo"`. +> - `va)` → 会选择`("foo")`. +> - `v2i)` → 会选择 `map (+) ("foo")` +> - `v2a)` → 会选择 `(map (+) ("foo"))` + +![img](http://upload-images.jianshu.io/upload_images/3101171-0b109d66a6111c83.png) + +#### 块操作: `` + +块操作,典型的操作: `0 I-- [ESC]` + +- `^` → 到行头 +- `` → 开始块操作 +- `` → 向下移动 (你也可以使用 hjkl 来移动光标,或是使用%,或是别的) +- `I-- [ESC]` → I 是插入,插入“`--`”,按 ESC 键来为每一行生效。 + +![img](http://upload-images.jianshu.io/upload_images/3101171-8b093a0f65707949.gif?imageMogr2/auto-orient/strip) + +在 Windows 下的 vim,你需要使用 `` 而不是 `` ,`` 是拷贝剪贴板。 + +#### 自动提示: `` 和 `` + +在 Insert 模式下,你可以输入一个词的开头,然后按 `或是,自动补齐功能就出现了……` + +![img](http://upload-images.jianshu.io/upload_images/3101171-e2ae877e67880ff7.gif?imageMogr2/auto-orient/strip) + +#### 宏录制: `qa` 操作序列 `q`, `@a`, `@@` + +- `qa` 把你的操作记录在寄存器 `a。` +- 于是 `@a` 会 replay 被录制的宏。 +- `@@` 是一个快捷键用来 replay 最新录制的宏。 + +> **示例** +> +> 在一个只有一行且这一行只有“1”的文本中,键入如下命令: +> +> - ``` +> qaYpq +> ``` +> +> → +> +> - `qa` 开始录制 +> - `Yp` 复制行. +> - `` 增加 1. +> - `q` 停止录制. +> +> - `@a` → 在 1 下面写下 2 +> +> - `@@` → 在 2 正面写下 3 +> +> - 现在做 `100@@` 会创建新的 100 行,并把数据增加到 103. + +![img](http://upload-images.jianshu.io/upload_images/3101171-f1889f8bca723964.gif?imageMogr2/auto-orient/strip) + +#### 可视化选择: `v`,`V`,`` + +前面,我们看到了 ``的示例 (在 Windows 下应该是),我们可以使用 `v` 和 `V`。一但被选好了,你可以做下面的事: + +- `J` → 把所有的行连接起来(变成一行) +- `<` 或 `>` → 左右缩进 +- `=` → 自动给缩进 (注:这个功能相当强大,我太喜欢了) + +![img](http://upload-images.jianshu.io/upload_images/3101171-fe1e19983fca213f.gif?imageMogr2/auto-orient/strip) + +在所有被选择的行后加上点东西: + +- `` +- 选中相关的行 (可使用 `j` 或 `` 或是 `/pattern` 或是 `%` 等……) +- `$` 到行最后 +- `A`, 输入字符串,按 `ESC。` + +![img](http://upload-images.jianshu.io/upload_images/3101171-b192601247334c4e.gif?imageMogr2/auto-orient/strip) + +#### 分屏: `:split` 和 `vsplit`. + +下面是主要的命令,你可以使用 VIM 的帮助 `:help split`. 你可以参考本站以前的一篇文章[VIM 分屏](https://coolshell.cn/articles/1679.html)。 + +> - `:split` → 创建分屏 (`:vsplit`创建垂直分屏) +> - `` : dir 就是方向,可以是 `hjkl` 或是 ←↓↑→ 中的一个,其用来切换分屏。 +> - `_` (或 `|`) : 最大化尺寸 (| 垂直分屏) +> - `+` (或 `-`) : 增加尺寸 + +![img](http://upload-images.jianshu.io/upload_images/3101171-f329d01e299cb366.gif?imageMogr2/auto-orient/strip) + +## Vim Cheat Sheet + +> 本节内容的原文地址:[http://cenalulu.github.io/linux/all-vim-cheatsheat/](http://cenalulu.github.io/linux/all-vim-cheatsheat/) + +### 经典版 + +下面这个键位图应该是大家最常看见的经典版了。其实这个版本是一系列的入门教程键位图的组合结果。要查看不同编辑模式下的键位图,可以看[这里打包下载](http://www.viemu.com/a_vi_vim_graphical_cheat_sheet_tutorial.html) + +此外,[这里](http://blog.ngedit.com/vi-vim-cheat-sheet-sch.gif)还有简体中文版。 + +![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/os/linux/vim/vim-cheat-sheet.png) + +### 入门版 + +基本操作的入门版。[原版出处](https://github.com/ahrencode/Miscellaneous)还有 keynote 版本可供 DIY 以及其他相关有用的 cheatsheet。 + +![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/os/linux/vim/basic-vim-cheat-sheet.png) + +### 进阶版 + +下图是 300DPI 的超清大图,另外[查看原文](http://michael.peopleofhonoronly.com/vim/)还有更多版本:黑白,低分辨率,色盲等 + +![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/os/linux/vim/vim-cheat-sheet-for-programmers.png) + +### 增强版 + +下图是一个更新时间较新的现代版,含有的信息也更丰富。[原文链接](http://vimcheatsheet.com/) + +![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/os/linux/vim/vim-cheat-sheet-02.png) + +### 文字版 + +[原文链接](http://tnerual.eriogerg.free.fr/vimqrc.pdf) + +![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/os/linux/vim/vim-cheat-sheet-text-01.png) + +![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/os/linux/vim/vim-cheat-sheet-text-02.png) + +## 资料 + +- [简明 VIM 练级攻略](https://coolshell.cn/articles/5426.html) ,Vim 渐进学习内容来源于这篇文章,作为 Vim 新手,我觉得入门效果很好。 +- [vim 官方文档](https://vim.sourceforge.io/docs.php) +- [vim-galore](https://github.com/mhinz/vim-galore) +- [Vim 入门基础](http://www.jianshu.com/p/bcbe916f97e1) \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/09.zsh.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/09.zsh.md" new file mode 100644 index 0000000000..816b7cb4be --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/09.zsh.md" @@ -0,0 +1,142 @@ +--- +title: zsh +date: 2019-07-11 16:52:00 +order: 09 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 + - Zsh +permalink: /pages/078b3e/ +--- + +# oh-my-zsh 应用 + +## Zsh 简介 + +### Zsh 是什么 + +使用 Linux 的人都知道:**\*Shell* 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。\_Shell* 既是一种命令语言,又是一种程序设计语言**。 + +Shell 的类型有很多种,linux 下默认的是 bash,虽然 bash 的功能已经很强大,但对于以懒惰为美德的程序员来说,bash 的提示功能不够强大,界面也不够炫,并非理想工具。 + +[**Zsh**](http://www.zsh.org/) 也是一种 Shell(据传说 99% 的 Bash 操作 和 Zsh 是相同的),它的功能极其强大,只是配置过于复杂,起初只有极客才在用。后来,出现了一个名叫 [**oh-my-zsh**](https://github.com/robbyrussell/oh-my-zsh) 的开源项目,使用 zsh 就变得十分简易了。 + +## Zsh 安装 + +### 环境要求 + +- CentOS 6.7 64 bit +- root 用户 + +### 安装 zsh + +- 先看下你的 CentOS 支持哪些 shell:`cat /etc/shells`,正常结果应该是这样的: + +```bash +/bin/sh +/bin/bash +/sbin/nologin +/bin/dash +/bin/tcsh +/bin/csh +``` + +如果已经有 zsh ,那么我们就不必安装了。 + +- CentOS 安装:`sudo yum install -y zsh` +- Ubuntu 安装:`sudo apt-get install -y zsh` +- 检查系统的 shell:`cat /etc/shells`,你会发现多了一个:`/bin/zsh` + +### 安装 oh-my-zsh + +使用 [**Zsh**](http://www.zsh.org/),怎么能离开灵魂伴侣 [**oh-my-zsh**](https://github.com/robbyrussell/oh-my-zsh)? + +```bash +# 安装 oh-my-zsh +wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh +``` + +### 配置 oh-my-zsh + +#### 插件 + +> oh-my-zsh 插件太多,不一一列举,请参考:[oh-my-zsh 插件列表](https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins) + +- 启用 oh-my-zsh 中自带的插件。 +- 查看 oh-my-zsh 插件数:`ls -l /root/.oh-my-zsh/plugins |grep "^d"|wc -l` +- 编辑配置文件:`vim /root/.zshrc` +- 插件推荐: + - [`zsh-autosuggestions`](https://github.com/zsh-users/zsh-autosuggestions) + - 这个插件会对历史命令一些补全,类似 fish 终端 + - 安装,复制该命令:`git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-\~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions` - 编辑:`vim \~/.zshrc`,找到这一行,后括号里面的后面添加:`plugins=( 前面的一些插件名称,换行,加上:zsh-autosuggestions)` - 刷新下配置:`source \~/.zshrc` + - extract + - 功能强大的解压插件,所有类型的文件解压一个命令 x 全搞定,再也不需要去记 tar 后面到底是哪几个参数了。 + - z + - 强大的目录自动跳转命令,会记忆你曾经进入过的目录,用模糊匹配快速进入你想要的目录。 + - [`zsh-syntax-highlighting`](https://github.com/zsh-users/zsh-syntax-highlighting) + - 这个插件会对终端命令高亮显示,比如正确的拼写会是绿色标识,否则是红色,另外对于一些 shell 输出语句也会有高亮显示,算是不错的辅助插件 + - 安装,复制该命令:`git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-\~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting` + - 编辑:`vim \~/.zshrc`,找到这一行,后括号里面的后面添加:`plugins=( 前面的一些插件名称,换行,加上:zsh-syntax-highlighting)` - 刷新下配置:`source \~/.zshrc` + - [`wd`](https://github.com/mfaerevaag/wd) + - 简单地讲就是给指定目录映射一个全局的名字,以后方便直接跳转到这个目录,比如: + - 编辑配置文件,添加上 wd 的名字:`vim /root/.zshrc` + - 我常去目录:**/opt/setups**,每次进入该目录下都需要这样:`cd /opt/setups` + - 现在用 wd 给他映射一个快捷方式:`cd /opt/setups ; wd add setups` + - 以后我在任何目录下只要运行:`wd setups` 就自动跑到 /opt/setups 目录下了 + - [`autojump`](https://github.com/wting/autojump) + - 这个插件会记录你常去的那些目录,然后做一下权重记录,你可以用这个命令看到你的习惯:`j --stat`,如果这个里面有你的记录,那你就只要敲最后一个文件夹名字即可进入,比如我个人习惯的 program:`j program`,就可以直接到:`/usr/program` + - 插件下载:`wget https://github.com/downloads/wting/autojump/autojump_v21.1.2.tar.gz` + - 解压:`tar zxvf autojump_v21.1.2.tar.gz` + - 进入解压后目录并安装:`cd autojump_v21.1.2/ ; ./install.sh` + - 再执行下这个:`source /etc/profile.d/autojump.sh` + - 编辑配置文件,添加上 autojump 的名字:`vim /root/.zshrc` + +#### 主题 + +> oh-my-zsh 主题太多,不一一列举,请参考:[oh-my-zsh 主题列表](https://github.com/robbyrussell/oh-my-zsh/wiki/Themes) + +- 查看 oh-my-zsh 主题数:`ls -l /root/.oh-my-zsh/themes |grep "^-"|wc -l` +- 个人比较推荐的是(排名有先后): + - `ys` + - `agnoster` + - `avit` + - `blinks` +- 编辑配置文件:`vim /root/.zshrc` +- 配置好新主题需要重新连接 shell 才能看到效果 + +zsh 效果如下: + +![img](https://cloud.githubusercontent.com/assets/2618447/6316862/70f58fb6-ba03-11e4-82c9-c083bf9a6574.png) + +## 快捷键 + +- 呃,这个其实可以不用讲的,你自己用的时候你自己会发现的,各种便捷,特别是用 Tab 多的人一定会有各种惊喜的。 +- 使用 ctrl-r 来搜索命令历史记录。按完此快捷键后,可以输入关键命令词语,如果历史记录有含有此词语会显示出来。 +- 命令别名: - 在命令行中输入 alias 可以查看已经有的命令别名 - 自己新增一些别名,编辑文件:`vim \~/.zshrc`,在文件加入下面格式的命令,比如以下是网友提供的一些思路: + +```shell +alias cls='clear' +alias ll='ls -l' +alias la='ls -a' +alias grep="grep --color=auto" +alias -s html='vim' # 在命令行直接输入后缀为 html 的文件名,会在 Vim 中打开 +alias -s rb='vim' # 在命令行直接输入 ruby 文件,会在 Vim 中打开 +alias -s py='vim' # 在命令行直接输入 python 文件,会用 vim 中打开,以下类似 +alias -s js='vim' +alias -s c='vim' +alias -s java='vim' +alias -s txt='vim' +alias -s gz='tar -xzvf' # 在命令行直接输入后缀为 gz 的文件名,会自动解压打开 +alias -s tgz='tar -xzvf' +alias -s zip='unzip' +alias -s bz2='tar -xjvf' +``` + +## 参考资料 + +- [oh-my-zsh Github](https://github.com/robbyrussell/oh-my-zsh) \ No newline at end of file diff --git "a/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/README.md" "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/README.md" new file mode 100644 index 0000000000..f39e68f12f --- /dev/null +++ "b/source/_posts/14.\346\223\215\344\275\234\347\263\273\347\273\237/02.Linux/02.\345\267\245\345\205\267/README.md" @@ -0,0 +1,33 @@ +--- +title: README +date: 2023-11-27 11:04:31 +categories: + - 操作系统 + - Linux + - 工具 +tags: + - 操作系统 + - Linux + - 工具 +permalink: /pages/38874e/ +hidden: true +index: false +--- + +# Linux 工具 + +## 📖 内容 + +- [网络运维](01.network-ops.md) +- [Samba](02.samba.md) +- [NTP](03.ntp.md) +- [Firewalld](04.firewalld.md) +- [Iptables](05.iptables.md) +- [Crontab](06.crontab.md) +- [Systemd](07.systemd.md) +- [Vim](08.vim.md) +- [oh-my-zsh](09.zsh.md) + +## 🚪 传送 + +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/99.\345\210\206\345\270\203\345\274\217\351\235\242\350\257\225.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/99.\345\210\206\345\270\203\345\274\217\351\235\242\350\257\225.md" index d0de5ef3f8..ad9968bb7a 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/99.\345\210\206\345\270\203\345\274\217\351\235\242\350\257\225.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/99.\345\210\206\345\270\203\345\274\217\351\235\242\350\257\225.md" @@ -1,6 +1,7 @@ --- title: 分布式面试总结 date: 2018-07-10 16:02:00 +order: 99 categories: - 分布式 - 分布式综合 @@ -19,11 +20,11 @@ permalink: /pages/f9209d/ | 数据类型 | 可以存储的值 | 操作 | | -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | -| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作
对整数和浮点数执行自增或者自减操作 | -| LIST | 列表 | 从两端压入或者弹出元素
读取单个或者多个元素
进行修剪,只保留一个范围内的元素 | -| SET | 无序集合 | 添加、获取、移除单个元素
检查一个元素是否存在于集合中
计算交集、并集、差集
从集合里面随机获取元素 | -| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对
获取所有键值对
检查某个键是否存在 | -| ZSET | 有序集合 | 添加、获取、删除元素
根据分值范围或者成员来获取元素
计算一个键的排名 | +| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作
对整数和浮点数执行自增或者自减操作 | +| LIST | 列表 | 从两端压入或者弹出元素
读取单个或者多个元素
进行修剪,只保留一个范围内的元素 | +| SET | 无序集合 | 添加、获取、移除单个元素
检查一个元素是否存在于集合中
计算交集、并集、差集
从集合里面随机获取元素 | +| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对
获取所有键值对
检查某个键是否存在 | +| ZSET | 有序集合 | 添加、获取、删除元素
根据分值范围或者成员来获取元素
计算一个键的排名 | > [What Redis data structures look like](https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/) @@ -300,7 +301,7 @@ MQ 的常见问题有: ### Dubbo 的实现过程?
- +
节点角色: @@ -349,7 +350,7 @@ MQ 的常见问题有: ### Dubbo 集群容错策略 ? -
+
- **Failover** - 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 - **Failfast** - 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 - **Failsafe** - 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 @@ -418,7 +419,7 @@ Protocol Buffer 的数据压缩效果好(即序列化后的数据量体积小 ZooKeeper 是一个分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。
- +
1. 每个 Server 在内存中存储了一份数据; @@ -475,7 +476,7 @@ NIO 基于 Reactor,当 socket 有流可读或可写入 socket 时,操作系 > > Redis 分布式锁如何保证可重入性? > -> 详细内容请参考:[分布式锁](distributed/分布式锁.md) +> 详细内容请参考:[分布式锁](../11.分布式协同/01.分布式协同综合/06.分布式锁.md) 【答题思路】 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\237\272\347\241\200\347\220\206\350\256\272.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\237\272\347\241\200\347\220\206\350\256\272.md" index a1d20e5c46..da7c7277ae 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\237\272\347\241\200\347\220\206\350\256\272.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\237\272\347\241\200\347\220\206\350\256\272.md" @@ -1,6 +1,7 @@ --- title: 分布式基础理论 date: 2021-11-08 08:15:33 +order: 01 categories: - 分布式 - 分布式理论 @@ -60,4 +61,4 @@ permalink: /pages/286bb3/ - [CAP twelve years later: How the "rules" have changed](https://www.semanticscholar.org/paper/CAP-twelve-years-later%3A-How-the-%22rules%22-have-Brewer/c9c73f5a1668b8bf12aae2efb6ac5a5a2c34002a) - [CAP 定理的含义](https://www.ruanyifeng.com/blog/2018/07/cap.html) - by 阮一峰 - [神一样的 CAP 理论被应用在何方](https://juejin.im/post/5d720e86f265da03cc08de74) -- [BASE: An Acid Alternative](https://queue.acm.org/detail.cfm?id=1394128) +- [BASE: An Acid Alternative](https://queue.acm.org/detail.cfm?id=1394128) \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/02.\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/02.\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247.md" index f035aaf8ad..9eb24953f2 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/02.\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/02.\345\210\206\345\270\203\345\274\217\344\270\200\350\207\264\346\200\247.md" @@ -1,6 +1,7 @@ --- title: 分布式一致性 date: 2021-11-08 08:15:33 +order: 02 categories: - 分布式 - 分布式理论 @@ -39,8 +40,6 @@ ACID 是数据库事务正确执行的四个基本要素。 - 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 - 事务满足持久化是为了能应对系统崩溃的情况。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) - ## 本地事务和分布式事务 学习分布式之前,先了解一下本地事务的概念。 @@ -49,7 +48,7 @@ ACID 是数据库事务正确执行的四个基本要素。 事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310092226555.png) **分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性**。 @@ -67,11 +66,11 @@ ACID 是数据库事务正确执行的四个基本要素。 **CAP 定理**,指的是:**在一个分布式系统中, 最多只能同时满足其中两项**。 - +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200746619.png) CAP 就是取 Consistency、Availability、Partition Tolerance 的首字母而命名。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20211102180526.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20211102180526.png) - 一致性(**C**onsistency):在任何给定时间,网络中的所有节点都具有完全相同(最近)的值。 - 可用性(**A**vailability):对网络的每个请求都会收到响应,但不能保证返回的数据是最新的。 @@ -148,7 +147,7 @@ CAP 就是取 Consistency、Availability、Partition Tolerance 的首字母而 选择 **AP** **模式**,实现了服务的高可用。用户访问系统的时候,都能得到响应数据,不会出现响应错误;但是,当出现分区故障时,相同的读操作,访问不同的节点,得到响应数据可能不一样。 - + (2)CP 模式 @@ -156,7 +155,7 @@ CAP 就是取 Consistency、Availability、Partition Tolerance 的首字母而 选择 **CP** **模式**,这样能够提供一部分的可用性。采用 CP 模型的分布式系统,一旦因为消息丢失、延迟过高发生了网络分区,就影响用户的体验和业务的可用性。因为为了防止数据不一致,集群将拒绝新数据的写入。 - + ## BASE 理论 @@ -172,7 +171,7 @@ BASE 理论的核心思想是:即使无法做到强一致性,但每个应用 - **软状态(Soft State)**指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即**允许系统不同节点的数据副本之间进行同步的过程存在延时**。 - **最终一致性(Eventually Consistent)**强调的是**系统中所有的数据副本,在经过一段时间的同步后,最终能达到一致的状态**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/%E5%88%86%E5%B8%83%E5%BC%8F%E7%90%86%E8%AE%BA-BASE.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/%E5%88%86%E5%B8%83%E5%BC%8F%E7%90%86%E8%AE%BA-BASE.png) ### BASE vs. ACID @@ -180,7 +179,7 @@ BASE 的理论的**核心思想**是:即使无法做到强一致性,但每 ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 要求最终一致性,通过**牺牲强一致性来达到可用性**,通常运用在大型分布式系统中。 - + 在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。 @@ -190,4 +189,4 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE - [CAP twelve years later: How the "rules" have changed](https://www.semanticscholar.org/paper/CAP-twelve-years-later%3A-How-the-%22rules%22-have-Brewer/c9c73f5a1668b8bf12aae2efb6ac5a5a2c34002a) - [CAP 定理的含义](https://www.ruanyifeng.com/blog/2018/07/cap.html) - by 阮一峰 - [神一样的 CAP 理论被应用在何方](https://juejin.im/post/5d720e86f265da03cc08de74) -- [BASE: An Acid Alternative](https://queue.acm.org/detail.cfm?id=1394128) \ No newline at end of file +- [BASE: An Acid Alternative](https://queue.acm.org/detail.cfm?id=1394128) diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/10.\346\213\234\345\215\240\345\272\255\345\260\206\345\206\233\351\227\256\351\242\230.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/10.\346\213\234\345\215\240\345\272\255\345\260\206\345\206\233\351\227\256\351\242\230.md" index ea92be8aa2..eeb03d5b89 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/10.\346\213\234\345\215\240\345\272\255\345\260\206\345\206\233\351\227\256\351\242\230.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/10.\346\213\234\345\215\240\345\272\255\345\260\206\345\206\233\351\227\256\351\242\230.md" @@ -1,6 +1,7 @@ --- title: 拜占庭将军问题 date: 2021-11-08 08:15:33 +order: 10 categories: - 分布式 - 分布式理论 @@ -33,7 +34,7 @@ permalink: /pages/a72fee/ 上述的故事可以映射到分布式系统中,_将军代表分布式系统中的节点;信使代表通信系统;叛徒代表故障或异常_。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210704104211.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210704104211.png) ## 问题分析 @@ -49,17 +50,18 @@ permalink: /pages/a72fee/ **示例一、叛徒人数为 1,将军人数为 3** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210704112012.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200748580.png) 这个示例中,将军人数不满足 3m + 1,无法保证忠诚的副官都执行将军的命令。 **示例二、叛徒人数为 1,将军人数为 4** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210704194815.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200748488.png) 这个示例中,将军人数满足 3m + 1,无论是副官中有叛徒,还是将军是叛徒,都能保证忠诚的副官执行将军的命令。 ## 参考资料 - [Wiki - 拜占庭将军问题](https://zh.wikipedia.org/wiki/%E6%8B%9C%E5%8D%A0%E5%BA%AD%E5%B0%86%E5%86%9B%E9%97%AE%E9%A2%98) -- [拜占庭将军问题视频讲解](https://www.bilibili.com/video/av78588312/) - 李永乐老师讲解的通俗易懂 \ No newline at end of file +- [拜占庭将军问题视频讲解](https://www.bilibili.com/video/av78588312/) - 李永乐老师讲解的通俗易懂 +- [The Byzantine Generals Problem](https://lamport.azurewebsites.net/pubs/byz.pdf) diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/11.Paxos\347\256\227\346\263\225.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/11.Paxos\347\256\227\346\263\225.md" index c422d5afeb..65b2c5e392 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/11.Paxos\347\256\227\346\263\225.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/11.Paxos\347\256\227\346\263\225.md" @@ -1,6 +1,8 @@ --- title: 深入剖析共识性算法 Paxos +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202310200757219.png date: 2020-02-02 22:00:00 +order: 11 categories: - 分布式 - 分布式理论 @@ -19,7 +21,7 @@ permalink: /pages/0276bb/ > > Paxos 算法解决的问题正是分布式一致性问题。在一个节点数为 2N+1 的分布式集群中,只要半数以上的节点(N + 1)还正常工作,整个系统仍可以正常工作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200202221611.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310200757219.png) ## Paxos 背景 @@ -42,7 +44,7 @@ Paxos 算法运行在允许宕机故障的异步系统中,不要求可靠的 Paxos 将分布式系统中的节点分 Proposer、Acceptor、Learner 三种角色。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210528150700.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210528150700.png) - **提议者(Proposer)**:发出提案(Proposal),用于投票表决。Proposal 信息包括提案编号 (Proposal ID) 和提议的值 (Value)。在绝大多数场景中,集群中收到客户端请求的节点,才是提议者。这样做的好处是,对业务代码没有入侵性,也就是说,我们不需要在业务代码中实现算法逻辑。 - **接受者(Acceptor)**:对每个 Proposal 进行投票,若 Proposal 获得多数 Acceptor 的接受,则称该 Proposal 被批准。一般来说,集群中的所有节点都在扮演接受者的角色,参与共识协商,并接受和存储数据。 @@ -89,18 +91,18 @@ Paxos 算法流程中的每条消息描述如下: 下图的示例中,首先客户端 1、2 作为提议者,分别向所有接受者发送包含提案编号的准备请求: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628231557.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628231557.png) 接着,当节点 A、B 收到提案编号为 1 的准备请求,节点 C 收到提案编号为 5 的准备请求后,将进行这样的处理: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628231908.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628231908.png) - 由于之前没有通过任何提案,所以节点 A、B 将返回一个 “尚无提案” 的响应。也就是说节点 A 和 B 在告诉提议者,我之前没有通过任何提案呢,并承诺以后不再响应提案编号小于等于 1 的准备请求,不会通过编号小于 1 的提案。 - 节点 C 也是如此,它将返回一个 “尚无提案”的响应,并承诺以后不再响应提案编号小于等于 5 的准备请求,不会通过编号小于 5 的提案。 另外,当节点 A、B 收到提案编号为 5 的准备请求,和节点 C 收到提案编号为 1 的准备请求的时候,将进行这样的处理过程: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628232029.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628232029.png) - 当节点 A、B 收到提案编号为 5 的准备请求的时候,因为提案编号 5 大于它们之前响应的准备请求的提案编号 1,而且两个节点都没有通过任何提案,所以它将返回一个 “尚无提案”的响应,并承诺以后不再响应提案编号小于等于 5 的准备请求,不会通过编号小于 5 的提案。 @@ -110,7 +112,7 @@ Paxos 算法流程中的每条消息描述如下: 首先客户端 1、2 在收到大多数节点的准备响应之后,会分别发送接受请求: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628232309.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628232309.png) - 当客户端 1 收到大多数的接受者(节点 A、B)的准备响应后,根据响应中提案编号最大的提案的值,设置接受请求中的值。因为该值在来自节点 A、B 的准备响应中都为空(也就是图 5 中的“尚无提案”),所以就把自己的提议值 3 作为提案的值,发送接受请求[1, 3]。 @@ -118,7 +120,7 @@ Paxos 算法流程中的每条消息描述如下: 当三个节点收到 2 个客户端的接受请求时,会进行这样的处理: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628232515.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628232515.png) - 当节点 A、B、C 收到接受请求[1, 3]的时候,由于提案的提案编号 1 小于三个节点承诺能通过的提案的最小提案编号 5,所以提案[1, 3]将被拒绝。 - 当节点 A、B、C 收到接受请求[5, 7]的时候,由于提案的提案编号 5 不小于三个节点承诺能通过的提案的最小提案编号 5,所以就通过提案[5, 7],也就是接受了值 7,三个节点就 X 值为 7 达成了共识。 @@ -170,4 +172,4 @@ Chubby 和 Boxwood 均使用 Multi Paxos。ZooKeeper 使用的 Zab 也是 Multi - [一致性算法(Paxos、Raft、Zab)](https://www.bilibili.com/video/BV1TW411M7Fx?from=search&seid=11524608198747599965) - [Raft 作者讲解 Paxos 视频](https://www.bilibili.com/video/av36556594) - [Paxos 算法讲解视频](https://www.youtube.com/watch?v=d7nAGI_NZPk) -- [分布式协议与算法实战](https://time.geekbang.org/column/intro/100046101) \ No newline at end of file +- [分布式协议与算法实战](https://time.geekbang.org/column/intro/100046101) diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/12.Raft\347\256\227\346\263\225.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/12.Raft\347\256\227\346\263\225.md" index a1de8f0314..be52a5da11 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/12.Raft\347\256\227\346\263\225.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/12.Raft\347\256\227\346\263\225.md" @@ -1,6 +1,7 @@ --- title: 深入剖析共识性算法 Raft date: 2020-02-01 22:07:00 +order: 12 categories: - 分布式 - 分布式理论 @@ -15,7 +16,7 @@ permalink: /pages/4907dc/ # 深入剖析共识性算法 Raft -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200201221202.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200201221202.png) ## Raft 简介 @@ -39,7 +40,7 @@ Paxos 和 Raft 都是分布式共识性算法,这个过程如同投票选举 **`复制状态机(Replicated State Machines)`** 是指一组服务器上的状态机产生相同状态的副本,并且在一些机器宕掉的情况下也可以继续运行。一致性算法管理着来自客户端指令的复制日志。状态机从日志中处理相同顺序的相同指令,所以产生的结果也是相同的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131233906.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131233906.png) 复制状态机通常都是基于复制日志实现的,如上图。每一个服务器存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。每一个日志都按照相同的顺序包含相同的指令,所以每一个服务器都执行相同的指令序列。因为每个状态机都是确定的,每一次执行操作都产生相同的状态和同样的序列。 @@ -79,7 +80,7 @@ Raft 将一致性问题分解成了三个子问题: - **`Follower`** - 跟随者,**不会发送任何请求**,只是简单的 **响应来自 Leader 或者 Candidate 的请求**。 - **`Candidate`** - 参选者,选举新 Leader 时的临时角色。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131215742.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131215742.png) > :bulb: 图示说明: > @@ -89,7 +90,7 @@ Raft 将一致性问题分解成了三个子问题: ### 任期 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131220742.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131220742.png) Raft 把时间分割成任意长度的 **_`任期(Term)`_**,任期用连续的整数标记。每一段任期从一次**选举**开始。**Raft 保证了在一个给定的任期内,最多只有一个领导者**。 @@ -159,29 +160,29 @@ Raft 算法使用随机选举超时时间的方法来确保很少会发生选票 (1)下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的选举超时时间之后,没收到 Leader 发来的心跳消息。因此,将 Term 由 0 增加为 1,转换为 Candidate,进入选举状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-candidate-01.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-candidate-01.gif) (2)此时,A 向所有其他节点发送投票请求。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-candidate-02.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-candidate-02.gif) (3)其它节点会对投票请求进行回复,如果超过半数以上的节点投票了,那么该 Candidate 就会立即变成 Term 为 1 的 Leader。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-candidate-03.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-candidate-03.gif) (4)Leader 会周期性地发送心跳消息给所有 Follower,Follower 接收到心跳包,会重新开始计时。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-candidate-04.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-candidate-04.gif) ### 多 Candidate 选举 (1)如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票。例如下图中 Candidate B 和 Candidate D 都发起 Term 为 4 的选举,且都获得两票,因此需要重新开始投票。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-multi-candidate-01.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-multi-candidate-01.gif) (2)当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-multi-candidate-02.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-multi-candidate-02.gif) ### 小结 @@ -207,7 +208,7 @@ Raft 日志同步保证如下两点: ### 日志复制流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200201115848.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200201115848.png) 1. Leader 负责处理所有客户端的请求。 2. Leader 把请求作为日志条目加入到它的日志中,然后并行的向其他服务器发送 `AppendEntries RPC` 请求,要求 Follower 复制日志条目。 @@ -220,19 +221,19 @@ Raft 日志同步保证如下两点: (1)来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-sync-log-01.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-sync-log-01.gif) (2)Leader 会把修改复制到所有 Follower。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-sync-log-02.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-sync-log-02.gif) (3)Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-sync-log-03.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-sync-log-03.gif) (4)此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/raft-sync-log-04.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/raft-sync-log-04.gif) ### 日志一致性 @@ -317,7 +318,7 @@ Raft 通过比较两份日志中最后一条日志条目的日志索引和 Term 当 Leader 要发送某个日志条目,落后太多的 Follower 的日志条目会被丢弃,Leader 会将快照发给 Follower。或者新上线一台机器时,也会发送快照给它。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200201220628.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200201220628.png) **生成快照的频率要适中**,频率过高会消耗大量 I/O 带宽;频率过低,一旦需要执行恢复操作,会丢失大量数据,影响可用性。推荐当日志达到某个固定的大小时生成快照。 @@ -332,6 +333,7 @@ Raft 通过比较两份日志中最后一条日志条目的日志索引和 Term - [Raft 作者讲解视频](https://www.youtube.com/watch?v=YbZ3zDzDnrw&feature=youtu.be) - [Raft 作者讲解视频对应的 PPT](http://www2.cs.uh.edu/~paris/6360/PowerPoint/Raft.ppt) - [分布式系统的 Raft 算法](https://www.jdon.com/artichect/raft.html) +- [Raft 算法详解](https://zhuanlan.zhihu.com/p/32052223) - [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) - 一个动画教程 - [The Raft Consensus Algorithm](https://raft.github.io/) - 一个交互式动画教程 - [sofa-jraft](https://github.com/sofastack/sofa-jraft) - 蚂蚁金服的 Raft 算法实现库(Java 版) \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/13.Gossip\347\256\227\346\263\225.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/13.Gossip\347\256\227\346\263\225.md" index 81e80d54a3..3f7cb61418 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/13.Gossip\347\256\227\346\263\225.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/13.Gossip\347\256\227\346\263\225.md" @@ -1,6 +1,7 @@ --- title: 分布式算法 Gossip date: 2021-07-13 09:18:41 +order: 13 categories: - 分布式 - 分布式理论 @@ -53,7 +54,7 @@ Gossip 过程是由种子节点发起,当一个种子节点有状态需要更 Goosip 协议的信息传播和扩散通常需要由种子节点发起。整个传播过程可能需要一定的时间,由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,因此它是一个**最终一致性**协议。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210708234308.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210708234308.gif) ## Gossip 类型 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/README.md" index 5506ace3e4..1fb5a75b1c 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/README.md" @@ -9,6 +9,7 @@ tags: - 理论 permalink: /pages/86cdf2/ hidden: true +index: false --- # 分布式理论 @@ -69,4 +70,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/02.\345\210\206\345\270\203\345\274\217\345\244\215\345\210\266.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/02.\345\210\206\345\270\203\345\274\217\345\244\215\345\210\266.md" index 582a72a3dd..83d3b20dd0 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/02.\345\210\206\345\270\203\345\274\217\345\244\215\345\210\266.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/02.\345\210\206\345\270\203\345\274\217\345\244\215\345\210\266.md" @@ -1,6 +1,7 @@ --- title: 分布式复制 date: 2022-06-11 10:40:10 +order: 02 categories: - 分布式 - 分布式协同 @@ -46,7 +47,7 @@ permalink: /pages/47f7bd/ 2. 其他副本则全部称为从副本(或称为从节点)。主副本把新数据写入本地存储后,然后将数据更改作为复制的日志或更改流发送给所有从副本。每个从副本获得更改日志之后将其应用到本地,且严格保持与主副本相同的写入顺序。 3. 客户端从数据库中读数据时,可以在主副本或者从副本上执行查询。再次强调,只有主副本才可以接受写请求:从客户端的角度来看,从副本都是只读的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220302202101.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220302202101.png) 典型应用: @@ -57,7 +58,7 @@ permalink: /pages/47f7bd/ 对于关系数据库系统,同步或异步通常是一个可配置的选项:而其他系统则可能是硬性指定或者只能二选一。同步复制与异步复制基本流程是,客户将更新请求发送给主节点,主节点接收到请求,接下来将数据更新转发给从节点。最后,由主节点来通知客户更新完成。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220302202158.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220302202158.png) 通常情况下, 复制速度会非常快,例如多数数据库系统可以在一秒之内完成所有从节点的更新。但是,系统其实并没有保证一定会在多段时间内完成复制。有些情况下,从节点可能落后主节点几分钟甚至更长时间,例如,由于从节点刚从故障中恢复,或者系统已经接近最大设计上限,或者节点之间的网络出现问题。 @@ -182,7 +183,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 然而对于异步复制存在这样一个问题,如图所示,用户在写入不久即查看数据,则新数据可能尚未到达从节点。对用户来讲, 看起来似乎是刚刚提交的数据丢失了,显然用户不会高兴。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220302204836.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220302204836.png) 对于这种情况,我们需要读写一致性。该机制保证如果用户重新加载页面, 他们总能看到自己最近提交的更新。但对其他用户则没有任何保证,这些用户的更新可能会在稍后才能刷新看到。如何实现呢?有以下几种可行性方案: @@ -198,7 +199,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 #### 单调读 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303093658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303093658.png) 用户看到了最新内容之后又读到了过期的内容,好像时间被回拨, 此时需要单调读一致性。 @@ -212,7 +213,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 如果数据库总是以相同的顺序写入,则读取总是看到一致的序列,不会发生这种反常。然而,在许多分布式数据库中,不同的分区独立运行,因此不存在全局写入顺序。这就导致当用户从数据库中读数据时,可能会看到数据库的某部分旧值和另一部分新值。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220613071613.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220613071613.png) 一个解决方案是确保任何具有因果顺序关系的写入都交给一个分区来完成,但该方案真实实现效率会大打折扣。现在有一些新的算法来显式地追踪事件因果关系。 @@ -276,7 +277,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 多主复制的最大问题是可能发生写冲突。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220613072848.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220613072848.png) #### 同步与异步冲突检测 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/03.\345\210\206\345\270\203\345\274\217\345\210\206\345\214\272.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/03.\345\210\206\345\270\203\345\274\217\345\210\206\345\214\272.md" index 8c9c71a798..3eb4f21da3 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/03.\345\210\206\345\270\203\345\274\217\345\210\206\345\214\272.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/03.\345\210\206\345\270\203\345\274\217\345\210\206\345\214\272.md" @@ -1,6 +1,7 @@ --- title: 分布式分区 date: 2022-06-14 08:49:21 +order: 03 categories: - 分布式 - 分布式协同 @@ -49,7 +50,7 @@ permalink: /pages/03714e/ 一且找到合适的关键宇 H 合希函数,就可以为每个分区分配一个哈希范围(而不是直接作用于关键宇范围),关键字根据其哈希值的范围划分到不同的分区中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303105925.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303105925.png) 这种方总可以很好地将关键字均匀地分配到多个分区中。分区边界可以是均匀间隔,也可以是伪随机选择( 在这种情况下,该技术有时被称为一致性哈希) 。 @@ -69,7 +70,7 @@ permalink: /pages/03714e/ ### 基于文档分区的二级索引 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303111528.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303111528.png) 在这种索引方法中,每个分区完全独立,各自维护自己的二级索引,且只负责自己分区内的文档而不关心其他分区中数据。每当需要写数据库时,包括添加,删除或更新文档等,只需要处理包含目标文档 ID 的那一个分区。因此文档分区索引也被称为本地索引,而不是全局索引。 @@ -81,7 +82,7 @@ permalink: /pages/03714e/ 为避免成为瓶颈,不能将全局索引存储在一个节点上,否则就破坏了设计分区均衡的目标。所以,全局索引也必须进行分区,且可以与数据关键字采用不同的分区策略。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303112708.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303112708.png) 可以直接通过关键词来全局划分索引,或者对其取哈希值。直接分区的好处是可以支持高效的区间查询;而采用哈希的方式则可以更均句的划分分区。 @@ -151,11 +152,11 @@ Cassandra 和 Ketama 则采用了第三种方式,使分区数与集群节点 2. 将所有客户端的请求都发送到一个路由层,由后者负责将请求转发到对应的分区节点上。路由层本身不处理任何请求,它仅充一个分区感知的负载均衡器。 3. 客户端感知分区和节点分配关系。此时,客户端可以直接连接到目标节点,而不需要任何中介。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220304120137.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220304120137.png) 许多分布式数据系统依靠独立的协调服务(如 ZooKeeper )跟踪集群范围内的元数据。每个节点都向 ZooKeeper 中注册自己, ZooKeeper 维护了分区到节点的最终映射关系。其他参与者(如路由层或分区感知的客户端)可以向 ZooKeeper 订阅此信息。一旦分区发生了改变,或者添加、删除节点, ZooKeeper 就会主动通知路由层,这样使路由信息保持最新状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220304163629.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220304163629.png) ## 参考资料 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/05.\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/05.\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" index 0d84bc4f5f..f3b30e22c9 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/05.\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/05.\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241.md" @@ -1,6 +1,7 @@ --- title: 分布式事务基本原理 date: 2019-06-21 11:30:00 +order: 05 categories: - 分布式 - 分布式协同 @@ -39,24 +40,20 @@ ACID 是数据库事务正确执行的四个基本要素。 - 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 - 可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 -**一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。** +一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性。 - 只有满足一致性,事务的执行结果才是正确的。 - 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。 - 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 - 事务满足持久化是为了能应对系统崩溃的情况。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) - ### 本地事务和分布式事务 学习分布式之前,先了解一下本地事务的概念。 -事务简单来说:**一个会话中所进行所有的操作,要么同时成功,要么同时失败**。 - -事务指的是满足 ACID 特性的一组操作,可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 +**事务指的是满足 ACID 特性的一组操作**。事务内的 SQL 语句,要么全执行成功,要么全执行失败。可以通过 `Commit` 提交一个事务,也可以使用 `Rollback` 进行回滚。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310092226555.png) **分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性。** @@ -66,11 +63,11 @@ ACID 是数据库事务正确执行的四个基本要素。 举个互联网常用的交易业务为例: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220512194132.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102324283.png) 上图中包含了库存和订单两个独立的微服务,每个微服务维护了自己的数据库。在交易系统的业务逻辑中,一个商品在下单之前需要先调用库存服务,进行扣除库存,再调用订单服务,创建订单记录。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220512194149.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102324139.png) 可以看到,如果多个数据库之间的数据更新没有保证事务,将会导致出现子系统数据不一致,业务出现问题。 @@ -125,7 +122,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State **情况 1,当所有参与者均反馈 yes,提交事务**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205153529.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102325237.png) > 1. 协调者向所有参与者发出正式提交事务的请求(即 commit 请求)。 > 2. 参与者执行 commit 请求,并释放整个事务期间占用的资源。 @@ -134,7 +131,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State **情况 2,当任何阶段 1 一个参与者反馈 no,中断事务**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205154145.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102325376.png) > 1. 协调者向所有参与者发出回滚请求(即 rollback 请求)。 > 2. 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。 @@ -172,7 +169,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State **情况 1:阶段 1 所有参与者均反馈 yes,参与者预执行事务**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205180242.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102326223.png) > 1. 协调者向所有参与者发出 preCommit 请求,进入准备阶段。 > 2. 参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。 @@ -180,7 +177,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State **情况 2:阶段 1 任何一个参与者反馈 no,或者等待超时后协调者尚无法收到所有参与者的反馈,即中断事务**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205205117.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102326360.png) > 1. 协调者向所有参与者发出 abort 请求。 > 2. 无论收到协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事务。 @@ -191,7 +188,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State **情况 1:阶段 2 所有参与者均反馈 ack 响应,执行真正的事务提交**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205180425.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102327231.png) > 1. 如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。 > 2. 参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。 @@ -200,7 +197,7 @@ BASE 是 **`基本可用(Basically Available)`**、**`软状态(Soft State **情况 2:任何一个参与者反馈 no,或者等待超时后协调者尚无法收到所有参与者的反馈,即中断事务**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205180515.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102327163.png) > 1. 如果协调者处于工作状态,向所有参与者发出 abort 请求。 > 2. 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。 @@ -253,13 +250,13 @@ TCC 事务的 Try、Confirm、Cancel 可以理解为 SQL 事务中的 Lock、Com **Confirm:当 Try 阶段服务全部正常执行, 执行确认业务逻辑操作** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205205200.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102329798.png) 这里使用的资源一定是 Try 阶段预留的业务资源。在 TCC 事务机制中认为,如果在 Try 阶段能正常的预留资源,那 Confirm 一定能完整正确的提交。Confirm 阶段也可以看成是对 Try 阶段的一个补充,Try+Confirm 一起组成了一个完整的业务逻辑。 **Cancel:当 Try 阶段存在服务执行失败, 进入 Cancel 阶段** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200205205221.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102329798.png) Cancel 取消执行,释放 Try 阶段预留的业务资源,上面的例子中,Cancel 操作会把冻结的库存释放,并更新订单状态为取消。 @@ -293,7 +290,7 @@ TCC 事务机制相对于传统事务机制(X/Open XA),TCC 事务机制相 整个业务处理流程如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220512194208.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102329044.png) > 1. **步骤 1 事务主动方处理本地事务。** 事务主动发在本地事务中处理业务更新操作和写消息表操作。 上面例子中库存服务阶段再本地事务中完成扣减库存和写消息表(图中 1、2)。 > 2. **步骤 2 事务主动方通过 MQ 通知事务被动方处理事务**。 消息中间件可以基于 Kafka、RocketMQ 消息队列,事务主动方法主动写消息到消息队列,事务消费方消费并处理消息队列中的消息。 上面例子中,库存服务把事务待处理消息写到消息中间件,订单服务消费消息中间件的消息,完成新增订单(图中 3 - 5)。 @@ -321,39 +318,40 @@ TCC 事务机制相对于传统事务机制(X/Open XA),TCC 事务机制相 ## MQ 事务 -事务消息需要消息队列提供相应的功能才能实现,Kafka 和 RocketMQ 都提供了事务相关功能。 +MQ 事务方案本质是利用 MQ 功能实现的本地消息表。事务消息需要消息队列提供相应的功能才能实现,Kafka 和 RocketMQ 都提供了事务相关功能。 - **Kafka** 的解决方案是:直接抛出异常,让用户自行处理。用户可以在业务代码中反复重试提交,直到提交成功,或者删除之前修改的数据记录进行事务补偿。 - **RocketMQ** 的解决方案是:通过事务反查机制来解决事务消息提交失败的问题。如果 Producer 在提交或者回滚事务消息时发生网络异常,RocketMQ 的 Broker 没有收到提交或者回滚的请求,Broker 会定期去 Producer 上反查这个事务对应的本地事务的状态,然后根据反查结果决定提交或者回滚这个事务。为了支撑这个事务反查机制,业务代码需要实现一个反查本地事务状态的接口,告知 RocketMQ 本地事务是成功还是失败。 -### RocketMQ 事务消息流程 - -基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。下面主要基于 RocketMQ 4.3 之后的版本介绍 MQ 的分布式事务方案。 - -在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ 的事务消息相对于普通 MQ,相对于提供了 2PC 的提交接口,方案如下: +### RocketMQ 事务消息实现 -**正常情况——事务主动方发消息** 这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下: +事务消息是 Apache RocketMQ 提供的一种高级消息类型,支持在分布式场景下保障消息生产和本地事务的最终一致性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220512194221.png) +**事务消息处理流程** -1. 发送方向 MQ 服务端(MQ Server)发送 half 消息。 -2. MQ Server 将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功。 -3. 发送方开始执行本地事务逻辑。 -4. 发送方根据本地事务执行结果向 MQ Server 提交二次确认(commit 或是 rollback)。 -5. MQ Server 收到 commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 rollback 状态则删除半消息,订阅方将不会接受该消息。 +事务消息交互流程如下图所示。 -**异常情况——事务主动方消息恢复** 在断网或者应用重启等异常情况下,图中 4 提交的二次确认超时未到达 MQ Server,此时处理逻辑如下: +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310102330908.png) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220512194230.png) +1. 生产者将消息发送至 Apache RocketMQ 服务端。 +2. Apache RocketMQ 服务端将消息持久化成功之后,向生产者返回 Ack 确认消息已经发送成功,此时消息被标记为"暂不能投递",这种状态下的消息即为半事务消息。 +3. 生产者开始执行本地事务逻辑。 +4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit 或是 Rollback),服务端收到确认结果后处理逻辑如下: + - 二次确认结果为 Commit:服务端将半事务消息标记为可投递,并投递给消费者。 + - 二次确认结果为 Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。 +5. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为 Unknown 未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。 **说明** 服务端回查的间隔时间和最大回查次数,请参见[参数限制](https://rocketmq.apache.org/zh/docs/introduction/03limits)。 +6. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。 +7. 生产者根据检查到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤 4 对半事务消息进行处理。 -5. MQ Server 对该消息发起消息回查。 -6. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。 -7. 发送方根据检查得到的本地事务的最终状态再次提交二次确认 -8. MQ Server 基于 commit / rollback 对消息进行投递或者删除 +**事务消息生命周期** ![事务消息](https://rocketmq.apache.org/zh/assets/images/lifecyclefortrans-fe4a49f1c9fdae5d590a64546722036f.png) -> **思考**:为什么不等待写业务表成功后再向消息队列发送提交消息呢? -> -> 因为可能存在这样情况:写业务表成功了,但是还没来得及发消息,节点就宕机了。 +- 初始化:半事务消息被生产者构建并完成初始化,待发送到服务端的状态。 +- 事务待提交:半事务消息被发送到服务端,和普通消息不同,并不会直接被服务端持久化,而是会被单独存储到事务存储系统中,等待第二阶段本地事务返回执行结果后再提交。此时消息对下游消费者不可见。 +- 消息回滚:第二阶段如果事务执行结果明确为回滚,服务端会将半事务消息回滚,该事务消息流程终止。 +- 提交待消费:第二阶段如果事务执行结果明确为提交,服务端会将半事务消息重新存储到普通存储系统中,此时消息对下游消费者可见,等待被消费者获取并消费。 +- 消费中:消息被消费者获取,并按照消费者本地的业务逻辑进行处理的过程。 此时服务端会等待消费者完成消费并提交消费结果,如果一定时间后没有收到消费者的响应,Apache RocketMQ 会对消息进行重试处理。具体信息,请参见[消费重试](https://rocketmq.apache.org/zh/docs/featureBehavior/10consumerretrypolicy)。 +- 消费提交:消费者完成消费处理,并向服务端提交消费结果,服务端标记当前消息已经被处理(包括消费成功和失败)。 Apache RocketMQ 默认支持保留所有消息,此时消息数据并不会立即被删除,只是逻辑标记已消费。消息在保存时间到期或存储空间不足被删除前,消费者仍然可以回溯消息重新消费。 +- 消息删除:Apache RocketMQ 按照消息保存机制滚动清理最早的消息数据,将消息从物理文件中删除。更多信息,请参见[消息存储和清理机制](https://rocketmq.apache.org/zh/docs/featureBehavior/11messagestorepolicy)。 ### MQ 事务方案总结 @@ -502,4 +500,5 @@ Saga 事务常见的有两种不同的实现方式:命令协调和事件编排 ## 参考资料 - [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html) -- [理解分布式事务](https://juejin.im/post/5c0e5bf8e51d45063322fe50) \ No newline at end of file +- [理解分布式事务](https://juejin.im/post/5c0e5bf8e51d45063322fe50) +- [RocketMQ 官方文档之事务消息](https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage) \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/06.\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/06.\345\210\206\345\270\203\345\274\217\351\224\201.md" index 09d6368400..89f307dd0f 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/06.\345\210\206\345\270\203\345\274\217\351\224\201.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/06.\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -1,6 +1,7 @@ --- title: 分布式锁基本原理 date: 2019-06-04 23:42:00 +order: 06 categories: - 分布式 - 分布式协同 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/README.md" index 01e1d0f109..cb3dd9abf2 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/01.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214\347\273\274\345\220\210/README.md" @@ -10,6 +10,7 @@ tags: - 分布式协同 permalink: /pages/2fe804/ hidden: true +index: false --- # 分布式协同 @@ -29,4 +30,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/01.ZooKeeper\345\216\237\347\220\206.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/01.ZooKeeper\345\216\237\347\220\206.md" index 3110087016..41d8b4a9de 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/01.ZooKeeper\345\216\237\347\220\206.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/01.ZooKeeper\345\216\237\347\220\206.md" @@ -1,6 +1,7 @@ --- title: ZooKeeper原理 date: 2020-06-02 22:28:54 +order: 01 categories: - 分布式 - 分布式协同 @@ -75,7 +76,7 @@ Zookeeper 服务是一个基于主从复制的高可用集群,集群中每个 树中的节点被称为 **`znode`**,其中根节点为 `/`,每个节点上都会保存自己的数据和节点信息。znode 可以用于存储数据,并且有一个与之相关联的 ACL(详情可见 [ACL](#ACL))。ZooKeeper 的设计目标是实现协调服务,而不是真的作为一个文件存储,因此 znode 存储数据的**大小被限制在 1MB 以内**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_1.png) **ZooKeeper 的数据访问具有原子性**。其读写操作都是要么全部成功,要么全部失败。 @@ -144,7 +145,7 @@ ZooKeeper 定义了如下五种权限: 由于处理读请求不需要服务器之间的交互,**Follower/Observer 越多,整体系统的读请求吞吐量越大**,也即读性能越好。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) ### 写操作 @@ -152,7 +153,7 @@ ZooKeeper 定义了如下五种权限: #### 写 Leader -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_4.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_4.png) 由上图可见,通过 Leader 进行写操作,主要分为五步: @@ -170,7 +171,7 @@ ZooKeeper 定义了如下五种权限: #### 写 Follower/Observer -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_5.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_5.png) - Follower/Observer 均可接受写请求,但不能直接处理,而需要将写请求转发给 Leader 处理。 - 除了多了一步请求转发,其它流程与直接写 Leader 无任何区别。 @@ -246,7 +247,7 @@ Zookeeper 中的所有数据其实都是由一个名为 `DataTree` 的数据结 通常来说,会话应该长期存在,而这需要由客户端来保证。客户端可以通过心跳方式(ping)来保持会话不过期。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602182239.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200602182239.png) ZooKeeper 的会话具有四个属性: @@ -340,7 +341,7 @@ ZAB 协议定义了两个可以**无限循环**的流程: 那么,ZooKeeper 是如何实现副本机制的呢?答案是:ZAB 协议的原子广播。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) ZAB 协议的原子广播要求: @@ -358,7 +359,7 @@ ZAB 协议的原子广播要求: 在分布式系统中,通常需要一个全局唯一的名字,如生成全局唯一的订单号等,ZooKeeper 可以通过顺序节点的特性来生成全局唯一 ID,从而可以对分布式系统提供命名服务。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602182548.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200602182548.png) ### 配置管理 @@ -372,7 +373,7 @@ ZAB 协议的原子广播要求: (1)访问 `/lock` (这个目录路径由程序自己决定),创建 **带序列号的临时节点(EPHEMERAL)** 。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602191358.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200602191358.png) (2)每个节点尝试获取锁时,拿到 `/locks`节点下的所有子节点(`id_0000`,`id_0001`,`id_0002`),**判断自己创建的节点是不是序列号最小的** @@ -380,11 +381,11 @@ ZAB 协议的原子广播要求: - 释放锁:执行完操作后,把创建的节点给删掉。 - 如果不是,则监听比自己要小 1 的节点变化。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602192619.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200602192619.png) (3)释放锁,即删除自己创建的节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602192341.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200602192341.png) 图中,NodeA 删除自己创建的节点 `id_0000`,NodeB 监听到变化,发现自己的节点已经是最小节点,即可获取到锁。 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/02.ZooKeeperJavaApi.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/02.ZooKeeperJavaApi.md" index ff0656e44f..f2224db842 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/02.ZooKeeperJavaApi.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/02.ZooKeeperJavaApi.md" @@ -1,6 +1,7 @@ --- title: ZooKeeperJavaApi date: 2022-02-19 13:27:21 +order: 02 categories: - 分布式 - 分布式协同 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/03.ZooKeeper\345\221\275\344\273\244.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/03.ZooKeeper\345\221\275\344\273\244.md" index fd7d0baaae..78f25d8a8d 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/03.ZooKeeper\345\221\275\344\273\244.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/03.ZooKeeper\345\221\275\344\273\244.md" @@ -1,6 +1,7 @@ --- title: ZooKeeper命令 date: 2022-02-19 13:27:21 +order: 03 categories: - 分布式 - 分布式协同 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/04.ZooKeeper\350\277\220\347\273\264.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/04.ZooKeeper\350\277\220\347\273\264.md" index 261a288f73..6c174b5abe 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/04.ZooKeeper\350\277\220\347\273\264.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/04.ZooKeeper\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: ZooKeeper运维 date: 2020-06-02 22:28:38 +order: 04 categories: - 分布式 - 分布式协同 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/05.ZooKeeperAcl.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/05.ZooKeeperAcl.md" index d33d83f9e4..ea7d484192 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/05.ZooKeeperAcl.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/05.ZooKeeperAcl.md" @@ -1,6 +1,7 @@ --- title: ZooKeeperAcl date: 2022-02-19 13:27:21 +order: 05 categories: - 分布式 - 分布式协同 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/README.md" index d559f03b2e..d5b156e057 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/02.ZooKeeper/README.md" @@ -10,6 +10,7 @@ tags: - 分布式协同 permalink: /pages/1b41b6/ hidden: true +index: false --- # ZooKeeper @@ -26,11 +27,11 @@ hidden: true ### [ZooKeeper 原理](01.ZooKeeper原理.md) -### [ZooKeeper 命令](02.ZooKeeper命令.md) +### [ZooKeeper Java Api](02.ZooKeeperJavaApi.md) -### [ZooKeeper 运维](03.ZooKeeper运维.md) +### [ZooKeeper 命令](03.ZooKeeper命令.md) -### [ZooKeeper Java Api](04.ZooKeeperJavaApi.md) +### [ZooKeeper 运维](04.ZooKeeper运维.md) ### [ZooKeeper Acl](05.ZooKeeperAcl.md) @@ -55,4 +56,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/README.md" index 42e0dc7502..5fab51a260 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/11.\345\210\206\345\270\203\345\274\217\345\215\217\345\220\214/README.md" @@ -9,6 +9,7 @@ tags: - 分布式协同 permalink: /pages/52c8b1/ hidden: true +index: false --- # 分布式协同 @@ -54,4 +55,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/01.\346\234\215\345\212\241\350\267\257\347\224\261.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/01.\346\234\215\345\212\241\350\267\257\347\224\261.md" index 4c372db214..1c44b7c06b 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/01.\346\234\215\345\212\241\350\267\257\347\224\261.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/01.\346\234\215\345\212\241\350\267\257\347\224\261.md" @@ -1,6 +1,7 @@ --- title: 服务路由 date: 2022-04-19 15:54:25 +order: 01 categories: - 分布式 - 分布式调度 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/02.\350\264\237\350\275\275\345\235\207\350\241\241.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/02.\350\264\237\350\275\275\345\235\207\350\241\241.md" index 692987c049..fe423b103c 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/02.\350\264\237\350\275\275\345\235\207\350\241\241.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/02.\350\264\237\350\275\275\345\235\207\350\241\241.md" @@ -1,6 +1,8 @@ --- title: 深入浅出负载均衡 date: 2018-07-05 15:50:00 +cover: https://raw.githubusercontent.com/dunwu/images/master/snap/202310250658719.png +order: 02 categories: - 分布式 - 分布式调度 @@ -13,31 +15,29 @@ permalink: /pages/98a1c1/ # 深入浅出负载均衡 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200528155252.png) - ## 负载均衡简介 -### 大型网站面临的挑战 +### 大型系统面临的挑战 -大型网站都要面对庞大的用户量,高并发,海量数据等挑战。 +大型系统通常要面对高并发、高可用、海量数据等挑战。 为了提升系统整体的性能,可以采用垂直扩展和水平扩展两种方式。 -- **垂直扩展**:在网站发展早期,可以从单机的角度通过**增加硬件处理能力**,比如 CPU 处理能力,内存容量,磁盘等方面,实现服务器处理能力的提升。但是,单机是有性能瓶颈的,一旦触及瓶颈,再想提升,付出的成本和代价会极高。这显然不能满足大型分布式系统(网站)所有应对的大流量,高并发,海量数据等挑战。 -- **水平扩展**:通过集群来分担大型网站的流量。集群中的应用服务器(节点)通常被设计成无状态,用户可以请求任何一个节点,这些节点共同分担访问压力。水平扩展有两个要点: - - **应用集群**:将同一应用部署到多台机器上,组成处理集群,接收负载均衡设备分发的请求,进行处理,并返回相应数据。 - - **负载均衡**:将用户访问请求,通过某种算法,分发到集群中的节点。 +- **垂直扩展**:在网站发展早期,可以从单机的角度通过**提升硬件处理能力**,比如 CPU 处理能力,内存容量,磁盘等方面,实现机器处理能力的提升。但是,单机是有性能瓶颈的,一旦触及瓶颈,再想提升,付出的成本和代价会极高。通俗来说,就三个字:**得加钱**!这显然不能满足大型分布式系统(网站)所有应对的大流量,高并发,海量数据等挑战。 +- **水平扩展**:通过集群来分担大型网站的流量。集群中的应用机器(节点)通常被设计成无状态,用户可以请求任何一个节点,这些节点共同分担访问压力。水平扩展有两个要点: + - **集群化、分区化**:将一个完整的应用化整为零,如果是无状态应用,可以直接集群化部署;如果是有状态应用,可以将状态数据分区(分片),然后部署到多台机器上。 + - **负载均衡**:集群化、分区化后,要解决的问题是,请求应该被分发(寻址)到哪台机器上。这就需要通过某种策略来控制分发,这种技术就是负载均衡。 ### 什么是负载均衡 -负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是 **尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性**。 +**“负载均衡(Load Balance,简称 LB)”是一种技术,用来在多个计算机、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到优化资源利用率、最大化吞吐率、最小化响应时间、同时避免过载的目的**。 负载均衡的主要作用如下: -- **高并发**:负载均衡通过算法调整负载,尽力均匀的分配应用集群中各节点的工作量,以此提高应用集群的并发处理能力(吞吐量)。 -- **伸缩性**:添加或减少服务器数量,然后由负载均衡进行分发控制。这使得应用集群具备伸缩性。 -- **高可用**:负载均衡器可以监控候选服务器,当服务器不可用时,自动跳过,将请求分发给可用的服务器。这使得应用集群具备高可用的特性。 -- **安全防护**:有些负载均衡软件或硬件提供了安全性功能,如:黑白名单处理、防火墙,防 DDos 攻击等。 +- **高并发**:负载均衡可以优化资源使用率,通过算法调整负载,尽力均匀的分配资源,以此提高资源利用率、从而提升整体吞吐量。 +- **伸缩性**:发生增减资源时,负载均衡可以自动调整分发,使得应用集群具备伸缩性。 +- **高可用**:负载均衡器可以监控候选机器,当某机器不可用时,自动跳过,将请求分发给可用的机器。这使得应用集群具备高可用的特性。 +- **安全防护**:有些负载均衡软件或硬件提供了安全性功能,如:黑白名单、防火墙,防 DDos 攻击等。 ## 负载均衡的分类 @@ -54,15 +54,15 @@ permalink: /pages/98a1c1/ 硬件负载均衡,一般是在定制处理器上运行的独立负载均衡服务器,**价格昂贵,土豪专属**。 -硬件负载均衡的 **主流产品** 有:[F5](https://f5.com/zh) 和 [A10](https://www.a10networks.com.cn/)。 +硬件负载均衡的**主流产品**有:[F5](https://f5.com/zh) 和 [A10](https://www.a10networks.com.cn/)。 -硬件负载均衡的 **优点**: +硬件负载均衡的**优点**: - **功能强大**:支持全局负载均衡并提供较全面的、复杂的负载均衡算法。 - **性能强悍**:硬件负载均衡由于是在专用处理器上运行,因此吞吐量大,可支持单机百万以上的并发。 - **安全性高**:往往具备防火墙,防 DDos 攻击等安全功能。 -硬件负载均衡的 **缺点**: +硬件负载均衡的**缺点**: - **成本昂贵**:购买和维护硬件负载均衡的成本都很高。 - **扩展性差**:当访问量突增时,超过限度不能动态扩容。 @@ -110,7 +110,7 @@ DNS 即 **域名解析服务**,是 OSI 第七层网络协议。DNS 被设计 DNS 负载均衡的工作原理就是:**基于 DNS 查询缓存,按照负载情况返回不同服务器的 IP 地址**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210117220007.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250643409.png) DNS 重定向的 **优点**: @@ -129,7 +129,7 @@ DNS 重定向的 **缺点**: HTTP 重定向原理是:**根据用户的 HTTP 请求计算出一个真实的服务器地址,将该服务器地址写入 HTTP 重定向响应中,返回给浏览器,由浏览器重新进行访问**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210117220310.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250643410.png) HTTP 重定向的 **优点**:**方案简单**。 @@ -152,11 +152,11 @@ HTTP 重定向的 **缺点**: - 正向代理:发生在 **客户端**,是由用户主动发起的。翻墙软件就是典型的正向代理,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。 - 反向代理:发生在 **服务端**,用户不知道代理的存在。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210117222209.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250643411.png) 反向代理是如何实现负载均衡的呢?以 Nginx 为例,如下所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/web/nginx/nginx-load-balance.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/web/nginx/nginx-load-balance.png) 首先,在代理服务器上设定好负载均衡规则。然后,当收到客户端请求,反向代理服务器拦截指定的域名或 IP 请求,根据负载均衡算法,将请求分发到候选服务器上。其次,如果某台候选服务器宕机,反向代理服务器会有容错处理,比如分发请求失败 3 次以上,将请求分发到其他候选服务器上。 @@ -177,7 +177,7 @@ HTTP 重定向的 **缺点**: IP 负载均衡是在网络层通过修改请求目的地址进行负载均衡。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210119000529.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250643413.png) 如上图所示,IP 均衡处理流程大致为: @@ -192,7 +192,7 @@ IP 负载均衡在内核进程完成数据分发,较反向代理负载均衡 数据链路层负载均衡是指在通信协议的数据链路层修改 mac 地址进行负载均衡。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210117222127.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250643412.png) 在 Linux 平台上最好的链路层负载均衡开源产品是 LVS (Linux Virtual Server)。 @@ -211,28 +211,60 @@ LVS 的工作流程大致如下: 负载均衡器的实现可以分为两个部分: -- 根据负载均衡算法在候选服务器列表选出一个服务器; -- 将请求数据发送到该服务器上。 +- 根据负载均衡算法在候选机器列表选出一个机器; +- 将请求数据发送到该机器上。 负载均衡算法是负载均衡服务核心中的核心。负载均衡产品多种多样,但是各种负载均衡算法原理是共性的。 -负载均衡算法有很多种,分别适用于不同的应用场景,本文仅介绍最为常见的负载均衡算法的特性及原理:轮询、随机、最小活跃数、源地址哈希、一致性哈希。 +负载均衡算法有很多种,分别适用于不同的应用场景。本章节将由浅入深的,逐一讲解各种负载均衡算法的策略和特性,并根据算法之间的互补关系将它们串联起来。 > 注:负载均衡算法的实现,推荐阅读 [Dubbo 官方负载均衡算法说明](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/) ,源码讲解非常详细,非常值得借鉴。 > -> 下文中的各种算法的可执行示例已归档在 Github 仓库:https://github.com/dunwu/java-tutorial/tree/master/codes/java-distributed/java-load-balance,可以通过执行 io.github.dunwu.javatech.LoadBalanceDemo 查看各算法执行效果。 +> 下文中的各种算法的可执行示例已归档在 Github 仓库:https://github.com/dunwu/java-tutorial/tree/master/codes/java-distributed/java-load-balance,可以通过执行 `io.github.dunwu.javatech.LoadBalanceDemo` 查看各算法执行效果。 + +### 轮询算法 + +**“轮询算法(Round Robin)”的策略是:将请求“依次”分发到候选机器**。 + +如下图所示,轮询负载均衡器收到来自客户端的 6 个请求,编号为 1、4 的请求会被发送到服务端 0;编号为 2、5 的请求会被发送到服务端 1;编号为 3、6 的请求会被发送到服务端 2。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250648178.png) + +**轮询算法适合的场景需要满足:各机器处理能力相近,且每个请求工作量差异不大**。 + +【示例】轮询负载均衡算法实现示例 + +```java +public class RoundRobinLoadBalance extends BaseLoadBalance implements LoadBalance { + + private final AtomicInteger position = new AtomicInteger(0); + + @Override + protected N doSelect(List nodes, String ip) { + int length = nodes.size(); + // 如果位置值已经等于节点数,重置为 0 + position.compareAndSet(length, 0); + N node = nodes.get(position.get()); + position.getAndIncrement(); + return node; + } + +} +``` + +### 随机算法 -### 随机 +**“随机算法(Random)” 将请求“随机”分发到候选机器**。 -#### 随机算法 +如下图所示,随机负载均衡器收到来自客户端的 6 个请求,会随机分发请求,可能会出现:编号为 1、5 的请求会被发送到服务端 0;编号为 2、4 的请求会被发送到服务端 1;编号为 3、6 的请求会被发送到服务端 2。 -**`随机(Random)`** 算法 **将请求随机分发到候选服务器**。 +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250648899.png) -随机算法 **适合服务器硬件相同的场景**。学习过概率论的都知道,调用量较小的时候,可能负载并不均匀,**调用量越大,负载越均衡**。 +**随机算法适合的场景需要满足:各机器处理能力相近,且每个请求工作量差异不大**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415165323.png) +学习过概率论的都知道,调用量较小的时候,可能负载并不均匀,**调用量越大,负载越均衡**。 -【示例】随机算法实现示例 +【示例】随机负载均衡算法实现示例 负载均衡接口 @@ -268,7 +300,7 @@ public abstract class BaseLoadBalance implements LoadBalance } ``` -服务器节点类 +机器节点类 ```java public class Node implements Comparable { @@ -300,11 +332,34 @@ public class RandomLoadBalance extends BaseLoadBalance implem } ``` -#### 加权随机算法 +### 加权轮询/随机算法 -**`加权随机(Weighted Random)`** 算法在随机算法的基础上,按照概率调整权重,进行负载分配。 +轮询/随机算法适合的场景都需要满足:各机器处理能力相近,且每个请求工作量差异不大。 -【示例】加权随机算法实现示例 +在理想状况下,假设每个机器的硬件条件相同,如:CPU、内存、网络 IO 等配置都相同;并且每个请求的耗时一样(请求传输时间、请求访问数据时间、计算时间等),这时轮询算法才能真正做到负载均衡。显然,要满足以上条件都相同是几乎不可能的,更不要说实际的网络通信中还有更多复杂的情况。 + +以上,如果有一点不能满足,都无法做到真正的负载均衡。个体存在较大差异,当请求量较大时,处理较慢的机器可能会逐渐积压请求,从而导致过载甚至宕机。 + +如下图所示,假设存在这样的场景: + +- 服务端 1 的处理能力远低于服务端 0 和服务端 2; +- 轮询/随机算法可以保证将请求尽量均匀的分发给两个机器; +- 编号为 1、4 的请求被发送到服务端 0;编号为 3、6 的请求被发送到服务端 2;二者处理能力强,应对游刃有余; +- 编号为 2、5 的请求被发送到服务端 1,服务端 1 处理能力弱,应对捉襟见肘,导致过载。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250649920.png) + +《蜘蛛侠》电影中有一句经典台词:**能力越大,责任越大**。显然,以上情况不符合这句话,处理能力强的机器并没有被分发到更多的请求,它的处理能力被闲置了。那么,如何解决这个问题呢? + +一种比较容易想到的思路是:引入权重属性,可以根据机器的硬件条件为其设置合理的权重值,负载均衡时,优先将请求分发到权重较高的机器。 + +“加权轮询算法(Weighted Round Robbin)” 和“加权随机算法(Weighted Random)” 都采用了加权的思路,在轮询/随机算法的基础上,引入了权重属性,优先将请求分发到权重较高的机器。这样,就可以针对性能高、处理速度快的机器设置较高的权重,让其处理更多的请求;而针对性能低、处理速度慢的机器则与之相反。一言以蔽之,加权策略强调了——能力越大,责任越大。 + +如下图所示,服务端 0 设置权重为 3,服务端 1 设置权重为 1,服务端 2 设置权重为 2。负载均衡器收到来自客户端的 6 个请求,那么编号为 1、2、5 的请求会被发送到服务端 0,编号为 4 的请求会被发送到服务端 1,编号为 3、6 的请求会被发送到机器 2。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250649943.png) + +【示例】加权随机负载均衡算法实现示例 ```java public class WeightRandomLoadBalance extends BaseLoadBalance implements LoadBalance { @@ -340,51 +395,7 @@ public class WeightRandomLoadBalance extends BaseLoadBalance } ``` -### 轮询 - -#### 轮询算法 - -**`轮询(Round Robin)`** 算法的策略是:**将请求依次分发到候选服务器**。 - -如下图所示,负载均衡器收到来自客户端的 6 个请求,(1, 3, 5) 的请求会被发送到服务器 1,(2, 4, 6) 的请求会被发送到服务器 2。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415164758.png) - -该算法适合场景:各服务器处理能力相近,且每个事务工作量差异不大。如果存在较大差异,那么处理较慢的服务器就可能会积压请求,最终无法承担过大的负载。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415165041.png) - -【示例】轮询算法示例 - -轮询负载均衡算法实现 - -```java -public class RoundRobinLoadBalance extends BaseLoadBalance implements LoadBalance { - - private final AtomicInteger position = new AtomicInteger(0); - - @Override - protected N doSelect(List nodes, String ip) { - int length = nodes.size(); - // 如果位置值已经等于节点数,重置为 0 - position.compareAndSet(length, 0); - N node = nodes.get(position.get()); - position.getAndIncrement(); - return node; - } - -} -``` - -#### 加权轮询算法 - -**`加权轮询(Weighted Round Robbin)`** 算法在轮询算法的基础上,增加了权重属性来调节转发服务器的请求数目。性能高、处理速度快的节点应该设置更高的权重,使得分发时优先将请求分发到权重较高的节点上。 - -如下图所示,服务器 A 设置权重为 5,服务器 B 设置权重为 1,负载均衡器收到来自客户端的 6 个请求,那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 A,(6) 请求会被发送到服务器 B。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415165140.png) - -【示例】加权轮询算法实现示例 +【示例】加权轮询负载均衡算法实现示例 以下实现基于 Dubbo 加权轮询算法做了一些简化。 @@ -539,30 +550,43 @@ public class WeightRoundRobinLoadBalance extends BaseLoadBalance } ``` -### 最小活跃数 +### 最少连接数算法 + +加权轮询/随机算法虽然一定程度上解决了机器处理能力不同时的负载均衡场景,但它最大的问题在于不能动态应对网络中负载不均的场景。加权的思路是在负载均衡处理的事前,预设好不同机器的权重,然后分发。然而,每个请求的连接时长不同,负载均衡器也不可能准确预估出请求的连接时长。因此,采用加权轮询/随机算法算法,都无法动态应对连接时长不均的网络场景,可能会出现**某些机器当前连接数过多,而另一些机器的连接过少**的情况,即并非真正的流量负载均衡。 + +如下图所示,假设存在这样的场景: + +- 3 个服务端的处理能力相同; +- 编号为 1、4 的请求被发送到服务端 0,但是 1 很快就断开连接,此时只有 4 请求连接服务端 0; +- 编号为 2、5 的请求被发送到服务端 1,但是 2 始终保持长连接;该系统继续运行时,服务端 1 发生过载; +- 编号为 3、6 的请求被发送到服务端 2,但是 3 很快就断开连接,此时只有 6 请求连接服务端 2; + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250650176.png) -**`最小活跃数(Least Active)`** 算法 **将请求分发到连接数/请求数最少的候选服务器**(目前处理请求最少的服务器)。 +既然,请求的连接时长不同,会导致有的服务端处理慢,积压大量连接数;而有的服务端处理快,保持的连接数少。那么,我们不妨想一下,如果负载均衡器监控一下服务端当前所持有的连接数,优先将请求分发给连接数少的服务端,不就能有效提高分发效率了吗?最少连接数算法正是采用这个思路去设计的。 -- 特点:根据候选服务器当前的请求连接数,动态分配。 -- 场景:**适用于对系统负载较为敏感或请求连接时长相差较大的场景**。 +**“最少连接数算法(Least Connections)” 将请求分发到连接数/请求数最少的候选机器**。 -由于每个请求的连接时长不一样,如果采用简单的轮循或随机算法,都可能出现**某些服务器当前连接数过大,而另一些服务器的连接过小**的情况,这就造成了负载并非真正均衡。虽然,轮询或算法都可以通过加权重属性的方式进行负载调整,但加权方式难以应对动态变化。 +要根据机器连接数分发,显然要先维护机器的连接数。因此,**最少连接数算法需要实时追踪每个候选机器的活跃连接数;然后,动态选出连接数最少的机器,优先分发请求**。最少连接数算法会记录当前时刻,每个候选节点正在处理的连接数,然后选择连接数最小的节点。该策略能够动态、实时地反应机器的当前状况,较为合理地将负责分配均匀,适用于对当前系统负载较为敏感的场景。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415171432.png) +由此可见,**最少连接数算法适用于对系统负载较为敏感且请求连接时长相差较大的场景**。 -最小活跃数算法会记录当前时刻,每个候选节点正在处理的连接数,然后选择连接数最小的节点。该策略能够动态、实时地反应服务器的当前状况,较为合理地将负责分配均匀,适用于对当前系统负载较为敏感的场景。 +如下图所示,假设存在这样的场景: -例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。 +- 服务端 0 和服务端 1 的处理能力相同; +- 编号为 1、3 的请求被发送到服务端 0,但是 1、3 很快就断开连接; +- 编号为 2、4 的请求被发送到服务端 1,但是 2、4 保持长连接; +- 由于服务端 0 当前连接数最少,编号为 5、6 的请求被分发到服务端 0。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415165935.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250650852.png) -**`加权最小活跃数(Weighted Least Connection)`**在最小活跃数的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。 +“加权最少连接数算法(Weighted Least Connection)”在最少连接数算法的基础上,根据机器的性能为每台机器分配权重,再根据权重计算出每台机器能处理的连接数。 -最小活跃数算法实现要点:活跃调用数越小,表明该服务节点处理能力越高,单位时间内可处理更多的请求,应优先将请求分发给该服务。在具体实现中,每个服务节点对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为 0。每收到一个请求,活跃数加 1,完成请求后则将活跃数减 1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最小活跃数负载均衡算法的基本思想。 +【示例】最少连接数算法实现 -【示例】最小活跃数算法实现 +最少连接数算法实现要点:活跃调用数越小,表明该服务节点处理能力越高,单位时间内可处理更多的请求,应优先将请求分发给该服务。在具体实现中,每个服务节点对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为 0。每收到一个请求,活跃数加 1,完成请求后则将活跃数减 1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最少连接数负载均衡算法的基本思想。 -以下实现基于 Dubbo 最小活跃数负载均衡算法做了些许改动。 +以下实现基于 Dubbo 最少连接数负载均衡算法做了些许改动。 ```java public class LeastActiveLoadBalance extends BaseLoadBalance implements LoadBalance { @@ -574,13 +598,13 @@ public class LeastActiveLoadBalance extends BaseLoadBalance i int length = nodes.size(); // 最小的活跃数 int leastActive = -1; - // 具有相同“最小活跃数”的服务者提供者(以下用 Node 代称)数量 + // 具有相同“最少连接数”的服务者提供者(以下用 Node 代称)数量 int leastCount = 0; - // leastIndexs 用于记录具有相同“最小活跃数”的 Node 在 nodes 列表中的下标信息 + // leastIndexs 用于记录具有相同“最少连接数”的 Node 在 nodes 列表中的下标信息 int[] leastIndexs = new int[length]; int totalWeight = 0; - // 第一个最小活跃数的 Node 权重值,用于与其他具有相同最小活跃数的 Node 的权重进行对比, - // 以检测是否“所有具有相同最小活跃数的 Node 的权重”均相等 + // 第一个最少连接数的 Node 权重值,用于与其他具有相同最少连接数的 Node 的权重进行对比, + // 以检测是否“所有具有相同最少连接数的 Node 的权重”均相等 int firstWeight = 0; boolean sameWeight = true; @@ -589,7 +613,7 @@ public class LeastActiveLoadBalance extends BaseLoadBalance i N node = nodes.get(i); // 发现更小的活跃数,重新开始 if (leastActive == -1 || node.getActive() < leastActive) { - // 使用当前活跃数更新最小活跃数 leastActive + // 使用当前活跃数更新最少连接数 leastActive leastActive = node.getActive(); // 更新 leastCount 为 1 leastCount = 1; @@ -599,7 +623,7 @@ public class LeastActiveLoadBalance extends BaseLoadBalance i firstWeight = node.getWeight(); sameWeight = true; - // 当前 Node 的活跃数 node.getActive() 与最小活跃数 leastActive 相同 + // 当前 Node 的活跃数 node.getActive() 与最少连接数 leastActive 相同 } else if (node.getActive() == leastActive) { // 在 leastIndexs 中记录下当前 Node 在 nodes 集合中的下标 leastIndexs[leastCount++] = i; @@ -614,16 +638,16 @@ public class LeastActiveLoadBalance extends BaseLoadBalance i } } - // 当只有一个 Node 具有最小活跃数,此时直接返回该 Node 即可 + // 当只有一个 Node 具有最少连接数,此时直接返回该 Node 即可 if (leastCount == 1) { return nodes.get(leastIndexs[0]); } - // 有多个 Node 具有相同的最小活跃数,但它们之间的权重不同 + // 有多个 Node 具有相同的最少连接数,但它们之间的权重不同 if (!sameWeight && totalWeight > 0) { // 随机生成一个 [0, totalWeight) 之间的数字 int offsetWeight = random.nextInt(totalWeight); - // 循环让随机数减去具有最小活跃数的 Node 的权重值, + // 循环让随机数减去具有最少连接数的 Node 的权重值, // 当 offset 小于等于0时,返回相应的 Node for (int i = 0; i < leastCount; i++) { int leastIndex = leastIndexs[i]; @@ -641,15 +665,31 @@ public class LeastActiveLoadBalance extends BaseLoadBalance i } ``` -### 哈希 +### 最少响应时间算法 + +**“最少响应时间算法(Least Time)” 将请求分发到响应时间最短的候选机器**。最少响应时间算法和最少连接数算法二者的目标其实是殊途同归,都是动态调整,将请求尽量分发到处理能力强的机器上。不同点在于,最少连接数关注的维度是机器持有的连接数,而最少响应时间关注的维度是机器上一次响应时间哪个最短。理论上来说,持有的连接数少,响应时间短,都可以表明机器潜在的处理能力比较强。 + +**最少响应时间算法具有高度的敏感性、自适应性**。但是,由于它需要持续监控候选机器的响应时延,相比于监控候选机器的连接数,会显著增加监控的开销。此外,请求的响应时延并不一定能完全反应机器的处理能力,有可能某机器上一次处理的请求恰好是一个开销非常小的请求。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250650334.png) + +### 哈希算法 + +前面提到的负载均衡算法,都只适用于无状态应用。所谓无状态应用,意味着:请求无论分发到集群中的任意机器上,得到的响应都是相同的:然而,有状态服务则不然:请求分发到不同的机器上,得到的结果是不一样的。典型的无状态应用是普通的 Web 服务器;典型的有状态应用是各种分布式数据库(如:Redis、ElasticSearch 等),这些数据库存储了大量,乃至海量的数据,无法全部存储在一台机器上,为了提高整体容量以及吞吐量,采用了分区(分片)的设计,将数据化整为零的存储在不同机器上。 + +对于有状态应用,不仅仅需要保证负载的均衡,更为重要的是,需要保证针对相同数据的请求始终访问的是相同的机器,否则,就无法获取到正确的数据。 -**`哈希(Hash)`** 算法**根据一个 key (可以是唯一 ID、IP 等),通过哈希计算得到一个数值,用该数值在候选服务器列表的进行取模运算,得到的结果便是选中的服务器**。 +那么,如何解决有状态应用的负载均衡呢?有一种方案是哈希算法。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415172716.png) +**“哈希算法(Hash)” 根据一个 key (可以是唯一 ID、IP、URL 等),通过哈希函数计算得到一个数值,用该数值在候选机器列表的进行取模运算,得到的结果便是选中的机器**。 -可以保证同一 IP 的客户端的请求会转发到同一台服务器上,用来实现会话粘滞(Sticky Session)。 +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250652913.png) -- 特点:保证特定用户总是请求到相同的服务器,若服务器宕机,会话会丢失。 +这种算法可以保证,同一关键字(IP 或 URL 等)的请求,始终会被转发到同一台机器上。哈希负载均衡算法常被用于实现会话粘滞(Sticky Session)。 + +但是 ,哈希算法的问题是:当增减节点时,由于哈希取模函数的基数发生变化,会影响大部分的映射关系,从而导致之前的数据不可访问。要解决这个问题,就必须根据新的计算公式迁移数据。显然,如果数据量很大的情况下,迁移成本很高;并且,在迁移过程中,要保证业务平滑过渡,需要使用数据双写等较为复杂的技术手段。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250653034.png) 【示例】源地址哈希算法实现示例 @@ -674,28 +714,47 @@ public class IpHashLoadBalance extends BaseLoadBalance implem } ``` -### 一致性哈希 +### 一致性哈希算法 + +哈希算法的缺点是:当集群中出现增减节点时,由于哈希取模函数的基数发生变化,会导致大量集群中的机器不可用;需要通过代价高昂的数据迁移,来解决问题。那么,我们自然会希望有一种更优化的方案,来尽量减少影响的机器数。一致性哈希算法就是为了这个目标而应运而生。 + +一致性哈希算法对哈希算法进行了改良。**“一致性哈希算法(Consistent Hash)”,根据哈希算法将对应的 key 哈希到一个具有 2^32 个桶的空间,并且头尾相连(0 到 2^32-1),即一个闭合的环形,这个圆环被称为“哈希环”**。哈希算法是对节点的数量进行取模运算;而一致性哈希算法则是对 2^32 进行取模运算。 -**`一致性哈希(Consistent Hash)`** 算法的目标是:**相同的请求尽可能落到同一个服务器上**。 +**哈希环的空间是按顺时针方向组织的**,需要对指定 key 的数据进行读写时,会执行两步: -**一致性哈希** 可以很好的解决 **稳定性问题**,可以将所有的存储节点排列在首尾相接的 `Hash` 环上,每个 `key` 在计算 `Hash` 后会 **顺时针** 找到 **邻接** 的存储节点存放。而当有节点 **加入** 或 **退出** 时,仅影响该节点在 `Hash` 环上 **顺时针相邻** 的 **后续节点**。 +1. 先对 key 进行哈希计算,以确定 key 在环上的位置; +2. 然后根据这个位置,顺时针找到的第一个节点,就是 key 对应的节点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/partition-consistent-hash.png) +所以,**一致性哈希是将“存储节点”和“数据”都映射到一个顺时针排序的哈希环上**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250653412.png) + +一致性哈希算法会尽可能保证,相同的请求被分发到相同的机器上。**当出现增减节点时,只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响,不会引起剧烈变动**。 - **相同的请求**是指:一般在使用一致性哈希时,需要指定一个 key 用于 hash 计算,可能是:用户 ID、请求方 IP、请求服务名称,参数列表构成的串 -- **尽可能**是指:服务器可能发生上下线,少数服务器的变化不应该影响大多数的请求。 +- **尽可能**是指:哈希环上出现增减节点时,少数机器的变化不应该影响大多数的请求。 + +(1)增加节点 -当某台候选服务器宕机时,原本发往该服务器的请求,会基于虚拟节点,平摊到其它候选服务器,不会引起剧烈变动。 +如下图所示,假设,哈希环中新增了一个节点 S4,新增节点经过哈希计算映射到图中位置: -- **优点** +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250653974.png) -**加入** 和 **删除** 节点只影响 **哈希环** 中 **顺时针方向** 的 **相邻的节点**,对其他节点无影响。 +此时,只有 K1 收到影响;而 K0、K2 均不受影响。 -- **缺点** +(2)减少节点 -**加减节点** 会造成 **哈希环** 中部分数据 **无法命中**。当使用 **少量节点** 时,**节点变化** 将大范围影响 **哈希环** 中 **数据映射**,不适合 **少量数据节点** 的分布式方案。**普通** 的 **一致性哈希分区** 在增减节点时需要 **增加一倍** 或 **减去一半** 节点才能保证 **数据** 和 **负载的均衡**。 +如下图所示,假设,哈希环中减少了一个节点 S0: -> **注意**:因为 **一致性哈希分区** 的这些缺点,一些分布式系统采用 **虚拟槽** 对 **一致性哈希** 进行改进,比如 `Dynamo` 系统、Redis。 +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250653207.png) + +此时,只有 K0 收到影响;而 K1、K2 均不受影响。 + +**一致性哈希算法并不保证节点能够在哈希环上分布均匀**,由此而产生一个问题,哈希环上可能有大量的请求集中在一个节点上。从概率角度来看,**哈希环上的节点越多,分布就越均匀**。正因为如此,一致性哈希算法不适用于节点数过少的场景。 + +如下图所示:极端情况下,可能由于节点在哈希环上分布不均,有大量请求计算得到的 key 会被集中映射到少数节点,甚至某一个节点上。此外,节点分布不均匀的情况下,进行容灾与扩容时,哈希环上的相邻节点容易受到过大影响,从而引发雪崩式的连锁反应。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202310250654770.png) 【示例】一致性哈希算法示例 @@ -711,7 +770,7 @@ public class ConsistentHashLoadBalance extends BaseLoadBalance nodes, String ip) { // 分片数,这里设为节点数的 4 倍 Integer replicaNum = nodes.size() * 4; - // 获取 nodes 原始的 hashcode + // 获取 nodes 原始的 hashcode[11.分布式协同](..%2F11.%B7%D6%B2%BC%CA%BD%D0%AD%CD%AC) int identityHashCode = System.identityHashCode(nodes); // 如果 nodes 是一个新的 List 对象,意味着节点数量发生了变化 @@ -822,13 +881,46 @@ public class ConsistentHashLoadBalance extends BaseLoadBalance countList = new LinkedList<>(); + + public SlidingWindowRateLimiter(long qps, int shardNum) { + this(qps, 1000, TimeUnit.MILLISECONDS, shardNum); + } + + public SlidingWindowRateLimiter(long maxPermits, long period, TimeUnit timeUnit, int shardNum) { + this.maxPermits = maxPermits; + this.periodMillis = timeUnit.toMillis(period); + this.lastPeriodMillis = System.currentTimeMillis(); + this.shardPeriodMillis = timeUnit.toMillis(period) / shardNum; + this.shardNum = shardNum; + for (int i = 0; i < shardNum; i++) { + countList.add(new AtomicLong(0)); + } + } + + @Override + public synchronized boolean tryAcquire(int permits) { long now = System.currentTimeMillis(); - if (now < time + timeout) { - // 单位时间内 - reqCount.addAndGet(1); - return reqCount.get() <= limit; - } else { - // 超出单位时间 - time = now; - reqCount = new AtomicInteger(0); + if (now > lastPeriodMillis) { + for (int shardId = 0; shardId < shardNum; shardId++) { + long shardCount = countList.get(shardId).get(); + totalCount.addAndGet(-shardCount); + countList.set(shardId, new AtomicLong(0)); + lastPeriodMillis += shardPeriodMillis; + } + } + int shardId = (int) (now % periodMillis / shardPeriodMillis); + if (totalCount.get() + permits <= maxPermits) { + countList.get(shardId).addAndGet(permits); + totalCount.addAndGet(permits); return true; + } else { + return false; } } + } ``` -【示例】基于 Redis Lua 计数限流算法的实现 +### 滑动窗口限流算法 -```lua --- 实现原理 --- 每次请求都将当前时间,精确到秒作为 key 放入 Redis 中,超时时间设置为 2s, Redis 将该 key 的值进行自增 --- 当达到阈值时返回错误,表示请求被限流 --- 写入 Redis 的操作用 Lua 脚本来完成,利用 Redis 的单线程机制可以保证每个 Redis 请求的原子性 - --- 资源唯一标志位 -local key = KEYS[1] --- 限流大小 -local limit = tonumber(ARGV[1]) +#### 滑动窗口限流算法的原理 --- 获取当前流量大小 -local currentLimit = tonumber(redis.call('get', key) or "0") +滑动窗口限流算法是对固定窗口限流算法的改进,解决了临界问题。 -if currentLimit + 1 > limit then - -- 达到限流大小 返回 - return 0; -else - -- 没有达到阈值 value + 1 - redis.call("INCRBY", key, 1) - -- 设置过期时间 - redis.call("EXPIRE", key, 2) - return currentLimit + 1 -end -``` +滑动窗口限流算法的**基本策略**是: + +- 将固定时间窗口分片为多个子窗口,每个子窗口的访问次数独立统计; +- 当请求时间大于当前子窗口的最大时间时,则将当前子窗口废弃,并将计时窗口向前滑动,并将下一个子窗口置为当前窗口。 +- 要保证所有子窗口的统计数之和不能超过阈值。 + +滑动窗口限流算法就是针对固定窗口限流算法的更细粒度的控制,分片越多,则限流越精准。 -### 滑动窗口法 +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202401230748277.png) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210625180432.png) +#### 滑动窗口限流算法的利弊 -滑动窗口法的**原理**: +滑动窗口限流算法的**优点**是:在滑动窗口限流算法中,临界位置的突发请求都会被算到时间窗口内,因此可以解决计数器算法的临界问题。 -滑动窗口法是计数器算法的一种改进,**增加一个时间粒度的度量单位,将原来的一个时间窗口划分成多个时间窗口,并且不断向右滑动该窗口**。流量经过滑动时间窗口算法整形之后,可以保证任意时间窗口内,都不会超过最大允许的限流值,从流量曲线上来看会更加平滑,可以部分解决上面提到的临界突发流量问题。 +滑动窗口限流算法的**缺点**是: -对比固定时间窗口限流算法,滑动时间窗口限流算法的时间窗口是持续滑动的,并且除了需要一个计数器来记录时间窗口内接口请求次数之外,还需要记录在时间窗口内每个接口请求到达的时间点,对内存的占用会比较多。 在临界位置的突发请求都会被算到时间窗口内,因此可以解决计数器算法的临界问题, +- **额外的内存开销** - 滑动时间窗口限流算法的时间窗口是持续滑动的,并且除了需要一个计数器来记录时间窗口内接口请求次数之外,还需要记录在时间窗口内每个接口请求到达的时间点,所以存在额外的内存开销。 +- **限流的控制粒度受限于窗口分片粒度** - 滑动窗口限流算法,**只能在选定的时间粒度上限流,对选定时间粒度内的更加细粒度的访问频率不做限制**。但是,由于每个分片窗口都有额外的内存开销,所以也并不是分片数越多越好的。 -比如在上文的例子中,通过滑动窗口算法整型后,第一个 1s 的时间窗口的 100 次请求都会通过,第二个时间窗口最开始的 10ms 内的 100 个请求都会被限流熔断。 +#### 滑动窗口限流算法的实现 -滑动窗口法的**缺陷**:基于时间窗口的限流算法,**只能在选定的时间粒度上限流,对选定时间粒度内的更加细粒度的访问频率不做限制**。 +【示例】Java 版本的滑动窗口限流算法 ```java -import java.util.Iterator; -import java.util.Random; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.stream.IntStream; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; -public class TimeWindow { - private ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); +public class SlidingWindowRateLimiter implements RateLimiter { /** - * 间隔秒数 + * 允许的最大请求数 */ - private int seconds; + private final long maxPermits; /** - * 最大限流 + * 窗口期时长 */ - private int max; - - public TimeWindow(int max, int seconds) { - this.seconds = seconds; - this.max = max; - - /** - * 永续线程执行清理queue 任务 - */ - new Thread(() -> { - while (true) { - try { - // 等待 间隔秒数-1 执行清理操作 - Thread.sleep((seconds - 1) * 1000L); - } catch (InterruptedException e) { - e.printStackTrace(); - } - clean(); - } - }).start(); - - } - - public static void main(String[] args) throws Exception { - - final TimeWindow timeWindow = new TimeWindow(10, 1); - - // 测试3个线程 - IntStream.range(0, 3).forEach((i) -> { - new Thread(() -> { - - while (true) { - - try { - Thread.sleep(new Random().nextInt(20) * 100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - timeWindow.take(); - } - - }).start(); - - }); - - } + private final long periodMillis; /** - * 获取令牌,并且添加时间 + * 分片窗口期时长 */ - public void take() { + private final long shardPeriodMillis; + /** + * 窗口期截止时间 + */ + private long lastPeriodMillis; - long start = System.currentTimeMillis(); - try { + /** + * 分片窗口数 + */ + private final int shardNum; + /** + * 请求总计数 + */ + private final AtomicLong totalCount = new AtomicLong(0); - int size = sizeOfValid(); - if (size > max) { - System.err.println("超限"); + /** + * 分片窗口计数列表 + */ + private final List countList = new LinkedList<>(); - } - synchronized (queue) { - if (sizeOfValid() > max) { - System.err.println("超限"); - System.err.println("queue中有 " + queue.size() + " 最大数量 " + max); - } - this.queue.offer(System.currentTimeMillis()); - } - System.out.println("queue中有 " + queue.size() + " 最大数量 " + max); + public SlidingWindowRateLimiter(long qps, int shardNum) { + this(qps, 1000, TimeUnit.MILLISECONDS, shardNum); + } + public SlidingWindowRateLimiter(long maxPermits, long period, TimeUnit timeUnit, int shardNum) { + this.maxPermits = maxPermits; + this.periodMillis = timeUnit.toMillis(period); + this.lastPeriodMillis = System.currentTimeMillis(); + this.shardPeriodMillis = timeUnit.toMillis(period) / shardNum; + this.shardNum = shardNum; + for (int i = 0; i < shardNum; i++) { + countList.add(new AtomicLong(0)); } - } - - public int sizeOfValid() { - Iterator it = queue.iterator(); - Long ms = System.currentTimeMillis() - seconds * 1000; - int count = 0; - while (it.hasNext()) { - long t = it.next(); - if (t > ms) { - // 在当前的统计时间范围内 - count++; + @Override + public synchronized boolean tryAcquire(int permits) { + long now = System.currentTimeMillis(); + if (now > lastPeriodMillis) { + for (int shardId = 0; shardId < shardNum; shardId++) { + long shardCount = countList.get(shardId).get(); + totalCount.addAndGet(-shardCount); + countList.set(shardId, new AtomicLong(0)); + lastPeriodMillis += shardPeriodMillis; } } - - return count; - } - - - /** - * 清理过期的时间 - */ - public void clean() { - Long c = System.currentTimeMillis() - seconds * 1000; - - Long tl = null; - while ((tl = queue.peek()) != null && tl < c) { - System.out.println("清理数据"); - queue.poll(); + int shardId = (int) (now % periodMillis / shardPeriodMillis); + if (totalCount.get() + permits <= maxPermits) { + countList.get(shardId).addAndGet(permits); + totalCount.addAndGet(permits); + return true; + } else { + return false; } } } ``` -### 漏桶法 +### 漏桶限流算法 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210625164126.png) +#### 漏桶限流算法的原理 -漏桶算法内部有一个容器,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。 +漏桶限流算法的**基本策略**是: -漏桶算法的本质是,**不管理请求量有多大,处理请求的速度始终是固定的**。这种模式类似生活中的漏斗,上宽下窄。请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在漏斗里。漏斗本身也有容量上限,如果桶满了,那么新进来的请求就丢弃。 +- 水(请求)以任意速率由入口进入到漏桶中; +- 水以固定的速率由出口出水(请求通过); +- 漏桶的容量是固定的,如果水的流入速率大于流出速率,最终会导致漏桶中的水溢出(这意味着请求拒绝)。 -漏桶算法的**优点**是:这种策略的好处是,做到了流量整形,即无论流量多大,即便是突发的大流量,输出依旧是一个稳定的流量。 +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202401230749486.png) -漏桶算法的**缺点**是:无法应对短时间的突刺流量。 +#### 漏桶限流算法的利弊 + +漏桶限流算法的**优点**是:**消费速率固定**——即无论流量多大,即便是突发的大流量,处理请求的速度始终是固定的。 + +漏桶限流算法的**缺点**是:不能灵活的调整流量。例如:一个集群通过增减节点的方式,弹性伸缩了其吞吐能力,漏桶限流算法无法随之调整。 **漏桶策略适用于间隔性突发流量且流量不用即时处理的场景**。 -【示例】漏桶法实现 +#### 漏桶限流算法的实现 + +【示例】Java 版本的漏桶限流算法 ```java -public class LeakBucket { +import java.util.concurrent.atomic.AtomicLong; + +public class LeakyBucketRateLimiter implements RateLimiter { + /** - * 时间 + * QPS */ - private long time; + private final int qps; + /** - * 总量 + * 桶的容量 */ - private Double total; + private final long capacity; + /** - * 水流出去的速度 + * 计算的起始时间 */ - private Double rate; + private long beginTimeMillis; + /** - * 当前总量 + * 桶中当前的水量 */ - private Double nowSize; + private final AtomicLong waterNum = new AtomicLong(0); + public LeakyBucketRateLimiter(int qps, int capacity) { + this.qps = qps; + this.capacity = capacity; + } - public boolean limit() { - long now = System.currentTimeMillis(); - nowSize = Math.max(0, (nowSize - (now - time) * rate)); - time = now; - if ((nowSize + 1) < total) { - nowSize++; + @Override + public synchronized boolean tryAcquire(int permits) { + + // 如果桶中没有水,直接通过 + if (waterNum.get() == 0) { + beginTimeMillis = System.currentTimeMillis(); + waterNum.addAndGet(permits); + return true; + } + + // 计算水量 + long leakedWaterNum = ((System.currentTimeMillis() - beginTimeMillis) / 1000) * qps; + long currentWaterNum = waterNum.get() - leakedWaterNum; + waterNum.set(Math.max(0, currentWaterNum)); + + // 重置时间 + beginTimeMillis = System.currentTimeMillis(); + + if (waterNum.get() + permits < capacity) { + waterNum.addAndGet(permits); return true; } else { return false; } - } + } ``` -### 令牌桶法 +### 令牌桶限流算法 + +#### 令牌桶限流算法的原理 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210625161944.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/202401230750231.png) 令牌桶算法的**原理**: @@ -299,50 +337,273 @@ public class LeakBucket { 2. 桶内最多存放 M 个 token,如果 token 到达时令牌桶已经满了,那么这个 token 就会被丢弃 3. 接口请求会先从令牌桶中取 token,拿到 token 则处理接口请求,拿不到 token 则进行限流处理 +#### 令牌桶限流算法的利弊 + 因为令牌桶存放了很多令牌,那么大量的突发请求会被执行,但是它不会出现临界问题,在令牌用完之后,令牌是以一个恒定的速率添加到令牌桶中的,因此不能再次发送大量突发请求。 规定固定容量的桶,token 以固定速度往桶内填充,当桶满时 token 不会被继续放入,每过来一个请求把 token 从桶中移除,如果桶中没有 token 不能请求。 **令牌桶算法适用于有突发特性的流量,且流量需要即时处理的场景**。 +#### 令牌桶限流算法的实现 + 【示例】Java 实现令牌桶算法 ```java -public class TokenBucket { +import java.util.concurrent.atomic.AtomicLong; + +/** + * 令牌桶限流算法 + * + * @author Zhang Peng + * @date 2024-01-18 + */ +public class TokenBucketRateLimiter implements RateLimiter { + /** - * 时间 + * QPS */ - private long time; + private final long qps; + /** - * 总量 + * 桶的容量 */ - private Double total; + private final long capacity; + /** - * token 放入速度 + * 上一次令牌发放时间 */ - private Double rate; + private long endTimeMillis; + /** - * 当前总量 + * 桶中当前的令牌数量 */ - private Double nowSize; + private final AtomicLong tokenNum = new AtomicLong(0); + public TokenBucketRateLimiter(long qps, long capacity) { + this.qps = qps; + this.capacity = capacity; + this.endTimeMillis = System.currentTimeMillis(); + } + + @Override + public synchronized boolean tryAcquire(int permits) { - public boolean limit() { long now = System.currentTimeMillis(); - nowSize = Math.min(total, nowSize + (now - time) * rate); - time = now; - if (nowSize < 1) { - // 桶里没有token + long gap = now - endTimeMillis; + + // 计算令牌数 + long newTokenNum = (gap * qps / 1000); + long currentTokenNum = tokenNum.get() + newTokenNum; + tokenNum.set(Math.min(capacity, currentTokenNum)); + + if (tokenNum.get() < permits) { return false; } else { - // 存在token - nowSize -= 1; + tokenNum.addAndGet(-permits); + endTimeMillis = now; return true; } } + +} +``` + +> **扩展** +> +> Guava 的 RateLimiter 工具类就是基于令牌桶算法实现,其源码分析可以参考:[RateLimiter 基于漏桶算法,但它参考了令牌桶算法](https://blog.csdn.net/forezp/article/details/100060686) + +### 限流算法测试 + +```java +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.RandomUtil; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class RateLimiterDemo { + + public static void main(String[] args) { + + // ============================================================================ + + int qps = 20; + + System.out.println("======================= 固定时间窗口限流算法 ======================="); + FixedWindowRateLimiter fixedWindowRateLimiter = new FixedWindowRateLimiter(qps); + testRateLimit(fixedWindowRateLimiter, qps); + + System.out.println("======================= 滑动时间窗口限流算法 ======================="); + SlidingWindowRateLimiter slidingWindowRateLimiter = new SlidingWindowRateLimiter(qps, 10); + testRateLimit(slidingWindowRateLimiter, qps); + + System.out.println("======================= 漏桶限流算法 ======================="); + LeakyBucketRateLimiter leakyBucketRateLimiter = new LeakyBucketRateLimiter(qps, 100); + testRateLimit(leakyBucketRateLimiter, qps); + + System.out.println("======================= 令牌桶限流算法 ======================="); + TokenBucketRateLimiter tokenBucketRateLimiter = new TokenBucketRateLimiter(qps, 100); + testRateLimit(tokenBucketRateLimiter, qps); + } + + private static void testRateLimit(RateLimiter rateLimiter, int qps) { + + AtomicInteger okNum = new AtomicInteger(0); + AtomicInteger limitNum = new AtomicInteger(0); + ExecutorService executorService = ThreadUtil.newFixedExecutor(10, "限流测试", true); + long beginTime = System.currentTimeMillis(); + + int threadNum = 4; + final CountDownLatch latch = new CountDownLatch(threadNum); + for (int i = 0; i < threadNum; i++) { + executorService.submit(() -> { + try { + batchRequest(rateLimiter, okNum, limitNum, 1000); + } catch (Exception e) { + log.error("发生异常!", e); + } finally { + latch.countDown(); + } + }); + } + + try { + latch.await(10, TimeUnit.SECONDS); + long endTime = System.currentTimeMillis(); + long gap = endTime - beginTime; + log.info("限流 QPS: {} -> 实际结果:耗时 {} ms,{} 次请求成功,{} 次请求被限流,实际 QPS: {}", + qps, gap, okNum.get(), limitNum.get(), okNum.get() * 1000 / gap); + if (okNum.get() == qps) { + log.info("限流符合预期"); + } + } catch (Exception e) { + log.error("发生异常!", e); + } finally { + executorService.shutdown(); + } + } + + private static void batchRequest(RateLimiter rateLimiter, AtomicInteger okNum, AtomicInteger limitNum, int num) + throws InterruptedException { + for (int j = 0; j < num; j++) { + if (rateLimiter.tryAcquire(1)) { + log.info("请求成功"); + okNum.getAndIncrement(); + } else { + log.info("请求限流"); + limitNum.getAndIncrement(); + } + TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(0, 10)); + } + } + } ``` +## 分布式限流 + +前文中,基于 Java 实现的限流算法示例只能运行在单节点,无法有效应对集群部署的服务,这中场景下就需要分布式限流。 + +实现分布式限流的一种简单解决方案是使用 Redis + Lua 来实现。使用二者来开发的原因是:1. Redis 的性能极高;2. Redis 支持以原子操作的方式执行 Lua 脚本。 + +### Redis + Lua 实现的固定窗口限流算法 + +Redis + Lua 实现的固定窗口限流算法实现思路: + +- 根据实际需要,将当前时间格式化为天(`yyyyMMdd`)、时(`yyyyMMddHH`)、分(`yyyyMMddHHmm`)、秒(`yyyyMMddHHmmss`),并作为 Redis 的 String 类型 Key。该 Key 可以视为一个固定时间窗口,其中的 value 用于统计访问量; +- 用于代表不同粒度的时间窗口按需设置过期时间; +- 一旦达到窗口的限流阈值时,请求被限流;否则请求通过。 + +【示例】Redis + Lua 实现的固定窗口限流算法 + +下面的代码片段模拟通过一个大小为 1 分钟的固定时间窗口进行限流,阈值为 100,过期时间 60s。 + +```java + private final String key = "rate:limit:202401222100"; + private final int limit = 100; + private final int seconds = 60; + + public boolean tryAcquire(int permits) { + // -- 缓存 Key + // local key = KEYS[1] + // -- 访问请求数 + // local permits = tonumber(ARGV[1]) + // -- 过期时间 + // local seconds = tonumber(ARGV[2]) + // -- 限流阈值 + // local limit = tonumber(ARGV[3]) + // + // -- 获取统计值 + // local count = tonumber(redis.call('GET', key) or "0") + // + // if count + permits > limit then + // -- 触发限流 + // return 0 + // else + // redis.call('INCRBY', key, permits) + // redis.call('EXPIRE', key, seconds) + // return count + permits + // end + String script = + "-- 缓存 Key\n" + + "local key = KEYS[1]\n" + + "-- 访问请求数\n" + + "local permits = tonumber(ARGV[1])\n" + + "-- 过期时间\n" + + "local seconds = tonumber(ARGV[2])\n" + + "-- 限流阈值\n" + + "local limit = tonumber(ARGV[3])\n" + + "\n" + + "-- 获取统计值\n" + + "local count = tonumber(redis.call('GET', key) or \"0\")\n" + + "\n" + + "if count + permits > limit then\n" + + " -- 触发限流\n" + + " return 0\n" + + "else\n" + + " redis.call('INCRBY', key, permits)\n" + + " redis.call('EXPIRE', key, seconds)\n" + + " return count + permits\n" + + "end"; + List keys = Collections.singletonList(key); + List args = Arrays.asList(String.valueOf(permits), String.valueOf(seconds), String.valueOf(limit)); + Object eval = jedis.eval(script, keys, args); + long value = (long) eval; + return value != 0; + } + + @Test + public void test() { + + for (int i = 0; i < 11; i++) { + if (tryAcquire(10)) { + System.out.println("请求成功"); + } else { + System.out.println("请求失败"); + } + } + } + // 请求成功 + // 请求成功 + // 请求成功 + // 请求成功 + // 请求成功 + // 请求成功 + // 请求成功 + // 请求成功 + // 请求成功 + // 请求成功 + // 请求失败 + // rate:limit:202401222100 统计值达到 100 +``` + +### Redis + Lua 实现的令牌桶限流算法 + 【示例】基于 Redis Lua 令牌桶限流算法实现 ```lua @@ -388,7 +649,6 @@ end redis.call("EXPIRE", bucketKey, 2) -- 限流判断 - if current_limit - permits < 1 then -- 达到限流大小 return 0 @@ -407,7 +667,7 @@ end 前面介绍了限流算法的基本原理和一些简单的实现。但在生产环境,我们一般应该使用更成熟的限流工具。 -- Guava 的 `RateLimiter`:RateLimiter 基于漏桶算法,但它参考了令牌桶算法。具体用法可以参考:[RateLimiter 基于漏桶算法,但它参考了令牌桶算法](https://blog.csdn.net/forezp/article/details/100060686) +- > Guava 的 `RateLimiter`:RateLimiter 基于漏桶算法,但它参考了令牌桶算法。具体用法可以参考:[RateLimiter 基于漏桶算法,但它参考了令牌桶算法](https://blog.csdn.net/forezp/article/details/100060686) - [Hystrix](https://github.com/Netflix/Hystrix):经典的限流、熔断工具,很值得借鉴学习。注:官方已停止发布版本。 - [Sentinel](https://github.com/alibaba/Sentinel):阿里的限流、熔断工具。 @@ -417,4 +677,4 @@ end - [谈谈限流算法的几种实现](https://www.jianshu.com/p/76cc8ba5ca91) - [如何限流?在工作中是怎么做的?说一下具体的实现?](https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/huifer-how-to-limit-current.md) - [浅析限流算法](https://gongfukangee.github.io/2019/04/04/Limit/) -- [RateLimiter 基于漏桶算法,但它参考了令牌桶算法](https://blog.csdn.net/forezp/article/details/100060686) \ No newline at end of file +- [RateLimiter 基于漏桶算法,但它参考了令牌桶算法](https://blog.csdn.net/forezp/article/details/100060686) diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/04.\345\210\206\345\270\203\345\274\217ID.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/04.\345\210\206\345\270\203\345\274\217ID.md" index 932583099c..bdcf749703 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/04.\345\210\206\345\270\203\345\274\217ID.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/04.\345\210\206\345\270\203\345\274\217ID.md" @@ -1,6 +1,7 @@ --- title: 分布式 ID 基本原理 date: 2019-07-24 11:55:00 +order: 04 categories: - 分布式 - 分布式调度 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/10.\345\210\206\345\270\203\345\274\217\344\274\232\350\257\235.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/10.\345\210\206\345\270\203\345\274\217\344\274\232\350\257\235.md" index 5e033c0487..0ee5fd1f7d 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/10.\345\210\206\345\270\203\345\274\217\344\274\232\350\257\235.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/10.\345\210\206\345\270\203\345\274\217\344\274\232\350\257\235.md" @@ -1,6 +1,7 @@ --- title: 分布式会话基本原理 date: 2019-06-04 23:42:00 +order: 10 categories: - 分布式 - 分布式调度 @@ -133,7 +134,7 @@ Token 的意思是“令牌”,是服务端生成的一串字符串,作为 > 缺点:**当服务器节点宕机时,将丢失该服务器节点上的所有 Session**。
- +
### Session 复制共享 @@ -143,7 +144,7 @@ Token 的意思是“令牌”,是服务端生成的一串字符串,作为 > 缺点:**占用过多内存**;**同步过程占用网络带宽以及服务器处理器时间**。
- +
### 基于缓存的 session 共享 @@ -153,7 +154,7 @@ Token 的意思是“令牌”,是服务端生成的一串字符串,作为 > 缺点:需要去实现存取 Session 的代码。
- +
## 具体实现 @@ -269,7 +270,7 @@ public class TestController { } ``` -上面的代码就是 ok 的,给 sping session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了。 +上面的代码就是 ok 的,给 spring session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了。 实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/README.md" index 6b3cfeda02..c489319dce 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/12.\345\210\206\345\270\203\345\274\217\350\260\203\345\272\246/README.md" @@ -9,6 +9,7 @@ tags: - 分布式调度 permalink: /pages/ba4012/ hidden: true +index: false --- # 分布式调度 @@ -23,4 +24,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/13.\345\210\206\345\270\203\345\274\217\351\253\230\345\217\257\347\224\250/02.\346\234\215\345\212\241\345\256\271\351\224\231.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/13.\345\210\206\345\270\203\345\274\217\351\253\230\345\217\257\347\224\250/02.\346\234\215\345\212\241\345\256\271\351\224\231.md" index 04a7a7ae5f..55b1a02c2b 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/13.\345\210\206\345\270\203\345\274\217\351\253\230\345\217\257\347\224\250/02.\346\234\215\345\212\241\345\256\271\351\224\231.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/13.\345\210\206\345\270\203\345\274\217\351\253\230\345\217\257\347\224\250/02.\346\234\215\345\212\241\345\256\271\351\224\231.md" @@ -1,6 +1,7 @@ --- title: 服务容错 date: 2022-04-20 17:27:42 +order: 02 categories: - 分布式 - 分布式高可用 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/01.RPC\345\237\272\347\241\200.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/01.RPC\345\237\272\347\241\200.md" index 72c0d37d77..dd34b11157 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/01.RPC\345\237\272\347\241\200.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/01.RPC\345\237\272\347\241\200.md" @@ -1,6 +1,7 @@ --- title: RPC 基础篇 date: 2020-06-10 16:00:00 +order: 01 categories: - 分布式 - 分布式通信 @@ -29,7 +30,7 @@ RPC 的主要作用是: ### RPC 的架构定位 -RPC 是微服务架构的基石,它提供了一种应用间通信的方式。![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619101023.png) +RPC 是微服务架构的基石,它提供了一种应用间通信的方式。![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619101023.png) ## RPC 核心原理 @@ -43,7 +44,7 @@ RPC 是一种应用间通信的方式,它的通信流程中需要注意以下 下图诠释了以上环节是如何串联起来的: - + ## RPC 协议 @@ -71,7 +72,7 @@ RPC 是一种应用间通信的方式,它的通信流程中需要注意以下 综上,一个 RPC 协议大概会由下图中的这些参数组成: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619102052.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619102052.png) ### 可扩展的协议 @@ -80,18 +81,18 @@ RPC 是一种应用间通信的方式,它的通信流程中需要注意以下 为了保证能平滑地升级改造前后的协议,我们有必要设计一种支持可扩展的协议。其关键在于让协议头支持可扩展,扩展后协议头的长度就不能定长了。那要实现读取不定长的协议头里面的内容,在这之前肯定需要一个固定的地方读取长度,所以我们需要一个固定的写入协议头的长度。整体协议就变成了三部分内容:固定部分、协议头内容、协议体内容。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619102833.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619102833.png) ## RPC 序列化 -> 有兴趣深入了解 JDK 序列化方式,可以参考:[Java 序列化](https://dunwu.github.io/blog/pages/2b2f0f/) +> 有兴趣深入了解 JDK 序列化方式,可以参考:[Java 序列化](https://dunwu.github.io/waterdrop/pages/2b2f0f/) 由于,网络传输的数据必须是二进制数据,而调用方请求的出参、入参都是对象。因此,必须将对象转换可传输的二进制,并且要求转换算法是可逆的。 - **序列化(serialize)**:序列化是将对象转换为二进制数据。 - **反序列化(deserialize)**:反序列化是将二进制数据转换为对象。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619110947.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619110947.png) 序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。 @@ -170,7 +171,7 @@ IO 多路复用分为 select,poll 和 epoll。 系统内核处理 IO 操作分为两个阶段——等待数据和拷贝数据。等待数据,就是系统内核在等待网卡接收到数据后,把数据写到内核中;而拷贝数据,就是系统内核在获取到数据后,将数据拷贝到用户进程的空间中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717154300) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717154300) 应用进程的每一次写操作,都会把数据写到用户空间的缓冲区中,再由 CPU 将数据拷贝到系统内核的缓冲区中,之后再由 DMA 将这份数据拷贝到网卡中,最后由网卡发送出去。这里我们可以看到,一次写操作数据要拷贝两次才能通过网卡发送出去,而用户进程的读操作则是将整个流程反过来,数据同样会拷贝两次才能让应用程序读取到数据。 @@ -178,7 +179,7 @@ IO 多路复用分为 select,poll 和 epoll。 所谓的零拷贝,就是取消用户空间与内核空间之间的数据拷贝操作,应用进程每一次的读写操作,可以通过一种方式,直接将数据写入内核或从内核中读取数据,再通过 DMA 将内核中的数据拷贝到网卡,或将网卡中的数据 copy 到内核。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717154716.jfif) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717154716.jfif) Netty 的零拷贝偏向于用户空间中对数据操作的优化,这对处理 TCP 传输中的拆包粘包问题有着重要的意义,对应用程序处理请求数据与返回数据也有重要的意义。 @@ -192,16 +193,16 @@ Netty 还提供 FileRegion 中包装 NIO 的 FileChannel.transferTo() 方法实 ## RPC 动态代理 -RPC 的远程过程调用时通过反射+动态代理实现的。 +RPC 的远程过程调用是通过反射+动态代理实现的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1553614585028.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1553614585028.png) RPC 框架会自动为要调用的接口生成一个代理类。当在项目中注入接口的时候,运行过程中实际绑定的就是这个接口生成的代理类。在接口方法被调用时,会被代理类拦截,这样,就可以在生成的代理类中,加入远程调用逻辑。 除了 JDK 默认的 `InvocationHandler` 能完成代理功能,还有很多其他的第三方框架也可以,比如像 Javassist、Byte Buddy 这样的框架。 -> 反射+动态代理更多详情可以参考:[深入理解 Java 反射和动态代理](https://dunwu.github.io/blog/pages/0d066a/) +> 反射+动态代理更多详情可以参考:[深入理解 Java 反射和动态代理](https://dunwu.github.io/waterdrop/pages/0d066a/) ## 参考资料 -- [《RPC 实战与核心原理》](https://time.geekbang.org/column/intro/280) \ No newline at end of file +- [RPC 实战与核心原理](https://time.geekbang.org/column/intro/100046201) \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/02.RPC\350\277\233\351\230\266.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/02.RPC\350\277\233\351\230\266.md" index e0f60a59f5..44481f44cf 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/02.RPC\350\277\233\351\230\266.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/02.RPC\350\277\233\351\230\266.md" @@ -1,6 +1,7 @@ --- title: RPC 进阶篇 date: 2022-06-19 22:04:59 +order: 02 categories: - 分布式 - 分布式通信 @@ -22,7 +23,7 @@ permalink: /pages/19f809/ 采用微内核架构的 RPC 架构模型: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200610164920.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200610164920.png) 在 RPC 框架里面,怎么支持插件化架构的呢?我们可以将每个功能点抽象成一个接 口,将这个接口作为插件的契约,然后把这个功能的接口与功能的实现分离,并提供接口的默认实现。在 Java 里面,JDK 有自带的 SPI(Service Provider Interface)服务发现机 @@ -44,7 +45,7 @@ RPC 框架必须要有服务注册和发现机制,这样,集群中的节点 搭建一个 ZooKeeper 集群作为注册中心集群,服务注册的时候只需要服务节点向 ZooKeeper 节点写入注册信息即可,利用 ZooKeeper 的 Watcher 机制完成服务订阅与服务下发功能 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200610180056.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200610180056.png) 通常我们可以使用 ZooKeeper、etcd 或者分布式缓存(如 Hazelcast)来解决事件通知问题,但当集群达到一定规模之后,依赖的 ZooKeeper 集群、etcd 集群可能就不稳定了,无法满足我们的需求。 @@ -63,7 +64,7 @@ ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的 而 RPC 框架的服务发现,在服务节点刚上线时,服务调用方是可以容忍在一段时间之后 (比如几秒钟之后)发现这个新上线的节点的。毕竟服务节点刚上线之后的几秒内,甚至更长的一段时间内没有接收到请求流量,对整个服务集群是没有什么影响的,所以我们可以牺牲掉 CP(强制一致性),而选择 AP(最终一致),来换取整个注册中心集群的性能和稳定性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717162006.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717162006.png) ## 健康检查 @@ -90,15 +91,15 @@ ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的 除了特殊场景的路由策略以外,对于机器中多个服务方,如何选择调用哪个服务节点,可以应用负载均衡策略。RPC 负载均衡策略一般包括随机、轮询、一致性 Hash、最近最少连接等。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717163401.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717163401.png) -> 负载均衡详情可以参考:[负载均衡基本原理](https://dunwu.github.io/blog/pages/98a1c1/) +> 负载均衡详情可以参考:[负载均衡基本原理](https://dunwu.github.io/waterdrop/pages/98a1c1/) ### 超时重试 超时重试机制是指:当调用端发起的请求失败或超时未收到响应时,RPC 框架自身可以进行重试,再重新发送请求,用户可以自行设置是否开启重试以及重试的次数。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200610193748.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200610193748.png) ### 限流、降级、熔断 @@ -110,11 +111,11 @@ ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的 如何避免服务停机带来的业务损失: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200610193806.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200610193806.png) 如何避免流量打到没有启动完成的节点: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200610193829.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200610193829.png) ## 容错处理 @@ -145,7 +146,7 @@ RPC 框架是不会知道哪些业务异常能够去进行异常重试的,我 综上,一个可靠的 RPC 容错处理机制如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717163921.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717163921.png) ## 优雅上线下线 @@ -157,7 +158,7 @@ RPC 框架是不会知道哪些业务异常能够去进行异常重试的,我 在 Java 语言里面,对应的是 Runtime.addShutdownHook 方法,可以注册关闭的钩子。在 RPC 启动的时候,我们提前注册关闭钩子,并在里面添加了两个处理程序,一个负责开启关闭标识,一个负责安全关闭服务对象,服务对象在关闭的时候会通知调用方下线节点。同时需要在我们调用链里面加上挡板处理器,当新的请求来的时候,会判断关闭标识,如果正在关闭,则抛出特定异常。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220630194749.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220630194749.png) ### 优雅上线 @@ -169,7 +170,7 @@ RPC 框架是不会知道哪些业务异常能够去进行异常重试的,我 调用方通过服务发现,除了可以拿到 IP 列表,还可以拿到对应的启动时间。我们需要把这个时间作用在负载均衡上。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220630194822.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220630194822.png) 通过这个小逻辑的改动,我们就可以保证当服务提供方运行时长小于预热时间时,对服务提供方进行降权,减少被负载均衡选择的概率,避免让应用在启动之初就处于高负载状态,从而实现服务提供方在启动后有一个预热的过程。 @@ -185,7 +186,7 @@ Bean 注册到 Spring-BeanFactory 里面去,而并不把这个 Bean 对应的 我们可以在服务提供方应用启动后,接口注册到注册中心前,预留一个 Hook 过程,让用户可以实现可扩展的 Hook 逻辑。用户可以在 Hook 里面模拟调用逻辑,从而使 JVM 指令能够预热起来,并且用户也可以在 Hook 里面事先预加载一些资源,只有等所有的资源都加载完成后,最后才把接口注册到注册中心。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220630194919.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220630194919.png) ## 限流熔断 @@ -204,7 +205,7 @@ Hook 逻辑。用户可以在 Hook 里面模拟调用逻辑,从而使 JVM 指 ## 业务分组 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200718204407.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200718204407.png) 在 RPC 里面我们可以通过分组的方式人为地给不同的调用方划分出不同的小集群,从而实现调用方流量隔离的效果,保障我们的核心业务不受非核心业务的干扰。但我们在考虑问题的时候,不能顾此失彼,不能因为新加一个的功能而影响到原有系统的稳定性。 @@ -218,4 +219,4 @@ Hook 逻辑。用户可以在 Hook 里面模拟调用逻辑,从而使 JVM 指 ## 参考资料 -- [《RPC 实战与核心原理》](https://time.geekbang.org/column/intro/280) \ No newline at end of file +- [RPC 实战与核心原理](https://time.geekbang.org/column/intro/100046201) \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/03.RPC\351\253\230\347\272\247.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/03.RPC\351\253\230\347\272\247.md" index df9934f34f..d55395cc8f 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/03.RPC\351\253\230\347\272\247.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/03.RPC\351\253\230\347\272\247.md" @@ -1,6 +1,7 @@ --- title: RPC 高级篇 date: 2022-06-23 17:04:13 +order: 03 categories: - 分布式 - 分布式通信 @@ -31,7 +32,7 @@ RPC 在整合分布式链路跟踪需要做的最核心的两件事就是“埋 其实,在 RPC 框架打印的异常信息中,是包括定位异常所需要的异常信息的,比如是哪类异常引起的问题(如序列化问题或网络超时问题),是调用端还是服务端出现的异常,调用端与服务端的 IP 是什么,以及服务接口与服务分组都是什么等等。具体如下图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719082205.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719082205.png) ## 泛化调用 @@ -39,11 +40,11 @@ RPC 在整合分布式链路跟踪需要做的最核心的两件事就是“埋 场景一:搭建一个统一的测试平台,可以让各个业务方在测试平台中通过输入接口、分组名、方法名以及参数值,在线测试自己发布的 RPC 服务。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719095518.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719095518.png) 场景二:搭建一个轻量级的服务网关,可以让各个业务方用 HTTP 的方式,通过服务网关调用其它服务。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200719095704.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200719095704.png) 为了解决这些场景的问题,可以使用泛化调用。 @@ -95,7 +96,7 @@ CompletableFuture $asyncInvoke(String methodName, String[] paramTypes 为了解决等待的耗时,可以使用**异步**。异步可以使用 Future 或 Callback 方式,Future 最为简单。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220630195115.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220630195115.png) 另外,我们可以通过对 CompletableFuture 的支持,实现 RPC 调用在调用端与服务端之间的完全异步,同时提升两端的单机吞吐量。 @@ -107,4 +108,4 @@ CompletableFuture $asyncInvoke(String methodName, String[] paramTypes ## 参考资料 -- [《RPC 实战与核心原理》](https://time.geekbang.org/column/intro/280) \ No newline at end of file +- [RPC 实战与核心原理](https://time.geekbang.org/column/intro/100046201) \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/11.\346\234\215\345\212\241\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/11.\346\234\215\345\212\241\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" index 8b8759bae8..8b65f41323 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/11.\346\234\215\345\212\241\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/00.RPC\347\273\274\345\220\210/11.\346\234\215\345\212\241\346\263\250\345\206\214\345\222\214\345\217\221\347\216\260.md" @@ -1,6 +1,7 @@ --- title: 服务注册和发现 date: 2022-04-18 19:34:47 +order: 11 categories: - 分布式 - 分布式通信 @@ -236,7 +237,7 @@ public void greet(String name) { 这个过程很像是生活中的房屋租赁,房东将租房信息挂到中介公司,房客从中介公司查找租房信息。房客如果想要租房东的房子,通过中介公司牵线搭桥,联系上房东,双方谈妥签订协议,就可以正式建立起租赁关系。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220415171843.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220415171843.png) 主流的服务注册与发现的解决方案,主要有两种: @@ -247,7 +248,7 @@ public void greet(String name) { **应用内注册与发现**方案是:注册中心提供服务端和客户端的 SDK,业务应用通过引入注册中心提供的 SDK,通过 SDK 与注册中心交互,来实现服务的注册和发现。最典型的案例要属 Netflix 开源的 Eureka,官方架构图如下: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220418204148.jfif) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220418204148.jfif) Eureka 的架构主要由三个重要的组件组成: @@ -259,7 +260,7 @@ Eureka 的架构主要由三个重要的组件组成: **应用外注册与发现**方案是:业务应用本身不需要通过 SDK 与注册中心打交道,而是通过其他方式与注册中心交互,间接完成服务注册与发现。最典型的案例是开源注册中心 Consul。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220418204352.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220418204352.png) Consul 实现应用外服务注册和发现主要依靠三个重要的组件: @@ -313,7 +314,7 @@ Consul 实现应用外服务注册和发现主要依靠三个重要的组件: 通过上面这种方式,ZooKeeper 保证了高可用性以及数据一致性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) ### 元数据存储 @@ -323,7 +324,7 @@ Consul 实现应用外服务注册和发现主要依靠三个重要的组件: - znode 可以包含数据和子 znode。 - znode 中的数据可以有多个版本,比如某一个 znode 下存有多个数据版本,那么查询这个路径下的数据需带上版本信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_1.png) ### 白名单机制 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo.md" index d0a23508e6..c6014e444a 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo.md" @@ -1,6 +1,7 @@ --- title: Dubbo 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - 分布式 - 分布式通信 @@ -36,7 +37,7 @@ RPC(Remote Procedure Call),即远程过程调用,它是一种通过网 #### RPC 工作流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200305121252.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200305121252.jpg) 1. 服务消费方(client)调用以本地调用方式调用服务; 2. client stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; @@ -187,7 +188,7 @@ Dubbo 支持多种配置方式: - Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。
- +
#### xml 配置 @@ -263,7 +264,7 @@ dubbo.registry.address=10.20.153.10:9090 #### 配置之间的关系
- +
#### 配置覆盖关系 @@ -276,7 +277,7 @@ dubbo.registry.address=10.20.153.10:9090 其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。
- +
### 动态配置中心 @@ -309,7 +310,7 @@ configCenter.setAddress("zookeeper://127.0.0.1:2181"); ### Dubbo 核心组件
- +
节点角色: @@ -349,7 +350,7 @@ configCenter.setAddress("zookeeper://127.0.0.1:2181"); ### Dubbo 架构层次
- +
图例说明: @@ -486,7 +487,7 @@ Dubbo 的 Hessian 协议可以和原生 Hessian 服务互操作,即: 在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
- +
- **Failover** - **失败自动切换**,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。 @@ -701,7 +702,7 @@ public class HelloServiceMock implements HelloService { 在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
- +
配置方式: diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo\345\277\253\351\200\237\345\205\245\351\227\250.md" index 74ae7ab73e..2111542d56 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/01.Dubbo/01.Dubbo\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Dubbo 快速入门 date: 2022-04-25 19:12:19 +order: 01 categories: - 分布式 - 分布式通信 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/22.Kong.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/22.Kong.md" index 150edc0d4f..2d09c1258c 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/22.Kong.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/22.Kong.md" @@ -1,6 +1,7 @@ --- title: kong date: 2018-10-11 13:08:18 +order: 22 categories: - 分布式 - 分布式通信 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/README.md" index 7aa2599a3e..755c50e347 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC/README.md" @@ -11,6 +11,7 @@ tags: - RPC permalink: /pages/a03b7b/ hidden: true +index: false --- # RPC @@ -22,10 +23,9 @@ hidden: true - [RPC 基础](00.RPC综合/01.RPC基础.md) - [RPC 进阶](00.RPC综合/02.RPC进阶.md) - [RPC 高级](00.RPC综合/03.RPC高级.md) -- [服务注册和发现](00.RPC综合/11.服务注册和发现.md) ## 📚 资料 ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/01.\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/01.\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225.md" index 9b8e1c5b60..fbb307c1b5 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/01.\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/01.\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225.md" @@ -1,6 +1,7 @@ --- title: 消息队列面试 date: 2022-02-17 22:34:30 +order: 01 categories: - 分布式 - 分布式通信 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/02.\346\266\210\346\201\257\351\230\237\345\210\227\345\237\272\346\234\254\345\216\237\347\220\206.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/02.\346\266\210\346\201\257\351\230\237\345\210\227\345\237\272\346\234\254\345\216\237\347\220\206.md" index ebac380930..7333f6e57b 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/02.\346\266\210\346\201\257\351\230\237\345\210\227\345\237\272\346\234\254\345\216\237\347\220\206.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/00.MQ\347\273\274\345\220\210/02.\346\266\210\346\201\257\351\230\237\345\210\227\345\237\272\346\234\254\345\216\237\347\220\206.md" @@ -1,6 +1,7 @@ --- title: 消息队列基本原理 date: 2019-07-05 15:11:00 +order: 02 categories: - 分布式 - 分布式通信 @@ -56,11 +57,11 @@ MQ 通信模型大致有以下类型: 假设这样一个场景,用户向系统 A 发起请求,系统 A 处理计算只需要 10 ms,然后通知系统 BCD 写库,系统 BCD 写库耗时分别为:100ms、200ms、300ms。最终总耗时为: 10+100ms+200ms+300ms=610ms。此外,加上请求和响应的网络传输时间,从用户角度看,可能要等待将近 1s 才能得到结果。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/theory/mq/mq_3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/theory/mq/mq_3.png) 如果使用 MQ,系统 A 接到请求后,耗时 10ms 处理计算,然后向系统 BCD 连续发送消息,假设耗时 5ms。那么 这一过程的总耗时为 3ms + 5ms = 8ms,这相比于 610 ms,大大缩短了响应时间。至于系统 BCD 的写库操作,只要自行消费 MQ 后处理即可,用户无需关注。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/theory/mq/mq_4.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/theory/mq/mq_4.png) ### 系统解耦 @@ -75,11 +76,11 @@ MQ 通信模型大致有以下类型: 如果需要和新的系统建立通信或删除已建立的通信,都需要修改代码,这种方案显然耦合度很高。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/theory/mq/mq_1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/theory/mq/mq_1.png) 如果使用 MQ,系统间的通信只需要通过发布/订阅(Pub/Sub)模型即可,彼此没有直接联系,也就不需要相互感知,从而达到 **解耦**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/theory/mq/mq_2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/theory/mq/mq_2.png) ### 流量削峰 @@ -91,11 +92,11 @@ MQ 通信模型大致有以下类型: 假设某个系统读写数据库的稳定性能为每秒处理 1000 条数据。平常情况下,远远达不到这么大的处理量。假设,因为因为做活动,系统的瞬时请求量剧增,达到每秒 10000 个并发请求,数据库根本承受不了,可能直接就把数据库给整崩溃了,这样系统服务就不可用了。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/theory/mq/mq_5.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/theory/mq/mq_5.png) 如果使用 MQ,每秒写入 10000 条请求,但是系统 A 每秒只从 MQ 中消费 1000 条请求,然后写入数据库。这样,就不会超过数据库的承受能力,而是把请求积压在 MQ 中。只要高峰期一过,系统 A 就会很快把积压的消息给处理掉。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/theory/mq/mq_6.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/theory/mq/mq_6.png) ### 传输缓冲 @@ -103,7 +104,7 @@ MQ 通信模型大致有以下类型: 例如,Kafka 常被用于做为各种日志数据、采集数据的数据中转。然后,Kafka 将数据转发给 Logstash、Elasticsearch 中,然后基于 Elasticsearch 来做日志中心,提供检索、聚合、分析日志的能力。开发者可以通过 Kibana 集成 Elasticsearch 数据进行可视化展示,或自行进行定制化开发。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200930164342.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200930164342.png) (2)MQ 也可以被用于流式处理。 @@ -154,7 +155,7 @@ MQ 主要引入了以下问题: Kafka 的客户端和 Broker 都会保存 Offset。客户端消费消息后,每隔一段时间,就把已消费的 Offset 提交给 Kafka Broker,表示已消费。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210427194009.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210427194009.png) 在这个过程中,如果客户端应用消费消息后,因为宕机、重启等情况而没有提交已消费的 Offset 。当系统恢复后,会继续消费消息,由于 Offset 未提交,就会出现重复消费的问题。 @@ -216,7 +217,7 @@ MQ 重复消费不可怕,可怕的是没有应对机制,可以借鉴的思 - 消费方维护 N 个缓存队列,具有相同 ID 的数据都写入同一个队列中; - 创建 N 个线程,每个线程只负责从指定的一个队列中取数据。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210427194215.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210427194215.png) ### 消息积压 @@ -248,7 +249,7 @@ MQ 重复消费不可怕,可怕的是没有应对机制,可以借鉴的思 - Kafka 日志的分区(Partition)分布在 Kafka 集群的节点上。每个节点在处理数据和请求时,共享这些分区。每一个分区都会在已配置的节点上进行备份,确保容错性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/mq/kafka/kafka-cluster-roles.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/mq/kafka/kafka-cluster-roles.png) #### Kafka 的副本机制 @@ -267,7 +268,7 @@ Kafka 在 0.8 以前的版本中,如果一个 Broker 宕机了,其上面的 **同一个 Topic 的不同 Partition 会分布在多个 Broker 上,而且一个 Partition 还会在其他的 Broker 上面进行备份**,Producer 在发布消息到某个 Partition 时,先找到该 Partition 的 Leader,然后向这个 Leader 推送消息;每个 Follower 都从 Leader 拉取消息,拉取消息成功之后,向 Leader 发送一个 ACK 确认。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/mq/kafka/kafka-replication.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/mq/kafka/kafka-replication.png) > FAQ > @@ -370,8 +371,6 @@ Kafka 在 0.8 以前的版本中,如果一个 Broker 宕机了,其上面的 #### (a) 主要特性 -![img](data:image/svg+xml;utf8,) - 1. 基于 **队列模型**:具有 **高性能**、**高可靠**、**高实时**、**分布式** 等特点; 2. `Producer`、`Consumer`、**队列** 都支持 **分布式**; 3. `Producer` 向一些队列轮流发送消息,**队列集合** 称为 `Topic`。`Consumer` 如果做 **广播消费**,则一个 `Consumer` 实例消费这个 `Topic` 对应的 **所有队列**;如果做 **集群消费**,则 **多个** `Consumer` 实例 **平均消费** 这个 `Topic` 对应的队列集合; @@ -411,8 +410,6 @@ Kafka 在 0.8 以前的版本中,如果一个 Broker 宕机了,其上面的 `Apache Kafka` 是一个 **分布式消息发布订阅** 系统。它最初由 `LinkedIn` 公司基于独特的设计实现为一个 **分布式的日志提交系统** (`a distributed commit log`),之后成为 `Apache` 项目的一部分。`Kafka` **性能高效**、**可扩展良好** 并且 **可持久化**。它的 **分区特性**,**可复制** 和 **可容错** 都是其不错的特性。 -![img](data:image/svg+xml;utf8,) - #### (a) 主要特性 1. **快速持久化**:可以在 `O(1)` 的系统开销下进行 **消息持久化**; @@ -492,6 +489,7 @@ MQ 的技术选型一般要考虑以下几点: #### P2P 模式
+ P2P 模式包含三个角色:MQ(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。 P2P 的特点 @@ -505,6 +503,7 @@ P2P 的特点 #### Pub/sub 模式
+ 包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到 Topic,系统将这些消息传递给多个订阅者。 Pub/Sub 的特点 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/01.Kafka\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/01.Kafka\345\277\253\351\200\237\345\205\245\351\227\250.md" index 61593d2238..082ed56c7a 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/01.Kafka\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/01.Kafka\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Kafka 快速入门 date: 2020-06-03 09:55:35 +order: 01 categories: - 分布式 - 分布式通信 @@ -20,7 +21,7 @@ permalink: /pages/a697a6/ > **Apache Kafka 是一款开源的消息引擎系统,也是一个分布式流计算平台,此外,还可以作为数据存储**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/mq/kafka/kafka-event-system.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/mq/kafka/kafka-event-system.png) ### Kafka 的功能 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/02.Kafka\347\224\237\344\272\247\350\200\205.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/02.Kafka\347\224\237\344\272\247\350\200\205.md" index 7a0b890dad..f83206860a 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/02.Kafka\347\224\237\344\272\247\350\200\205.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/02.Kafka\347\224\237\344\272\247\350\200\205.md" @@ -1,6 +1,7 @@ --- title: Kafka 生产者 date: 2021-04-14 15:05:34 +order: 02 categories: - 分布式 - 分布式通信 @@ -49,14 +50,14 @@ Kafka 生产者发送消息流程: - 如果**成功**,则返回一个 `RecordMetaData` 对象,它包含了主题、分区、偏移量; - 如果**失败**,则返回一个错误。生产者在收到错误后,可以进行重试,重试次数可以在配置中指定。失败一定次数后,就返回错误消息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200528224323.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200528224323.png) 生产者向 Broker 发送消息时是怎么确定向哪一个 Broker 发送消息? - 生产者会向任意 broker 发送一个元数据请求(`MetadataRequest`),获取到每一个分区对应的 Leader 信息,并缓存到本地。 - 生产者在发送消息时,会指定 Partition 或者通过 key 得到到一个 Partition,然后根据 Partition 从缓存中获取相应的 Leader 信息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621113043.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621113043.png) ## 生产者 API @@ -214,7 +215,7 @@ Kafka 的数据结构采用三级结构,即:主题(Topic)、分区(Par 在 Kafka 中,任意一个 Topic 维护了一组 Partition 日志,如下所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/mq/kafka/kafka-log-anatomy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/mq/kafka/kafka-log-anatomy.png) 每个 Partition 都是一个单调递增的、不可变的日志记录,以不断追加的方式写入数据。Partition 中的每条记录会被分配一个单调递增的 id 号,称为偏移量(Offset),用于唯一标识 Partition 内的每条记录。 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/03.Kafka\346\266\210\350\264\271\350\200\205.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/03.Kafka\346\266\210\350\264\271\350\200\205.md" index 5c27a1e211..d9708c5ccd 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/03.Kafka\346\266\210\350\264\271\350\200\205.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/03.Kafka\346\266\210\350\264\271\350\200\205.md" @@ -1,6 +1,7 @@ --- title: Kafka 消费者 date: 2021-04-14 15:05:34 +order: 03 categories: - 分布式 - 分布式通信 @@ -23,7 +24,7 @@ permalink: /pages/41a171/ - push 模式:MQ 推送数据给消费者 - pull 模式:消费者主动向 MQ 请求数据 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210425190248.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210425190248.png) Kafka 消费者(Consumer)以 pull 方式从 Broker 拉取消息。相比于 push 方式,pull 方式灵活度和扩展性更好,因为消费的主动性由消费者自身控制。 @@ -42,7 +43,7 @@ push 模式的优缺点: **一条消息只有被提交,才会被消费者获取到**。如下图,只能消费 Message0、Message1、Message2: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621113917.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621113917.png) ### 消费者群组 @@ -56,11 +57,11 @@ Kafka 消费者从属于消费者群组,**一个群组里的 Consumer 订阅 同一时刻,**一条消息只能被同一消费者组中的一个消费者实例消费**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210408194235.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210408194235.png) **不同消费者群组之间互不影响**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210408194839.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210408194839.png) ### 消费流程 @@ -69,7 +70,7 @@ Kafka 消费者通过 `poll` 来获取消息,但是获取消息时并不是立 - 消费者通过 `customer.poll(time)` 中设置等待时间 - Broker 会等待累计一定量数据,然后发送给消费者。这样可以减少网络开销。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210425194822.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210425194822.png) poll 除了获取消息外,还有其他作用: @@ -354,7 +355,7 @@ try { (2)消费者通过向被指派为群组协调器(Coordinator)的 Broker 定期发送心跳来维持它们和群组的从属关系以及它们对分区的所有权。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210415160730.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210415160730.png) (3)群主从群组协调器获取群组成员列表,然后给每一个消费者进行分配分区 Partition。有两种分配策略:Range 和 RoundRobin。 @@ -434,11 +435,11 @@ try { (1)**如果提交的偏移量小于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息就会被重复处理**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210412200354.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210412200354.png) (2)**如果提交的偏移量大于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210412200405.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210412200405.png) 由此可知,处理偏移量,会对客户端处理数据产生影响。 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/04.Kafka\351\233\206\347\276\244.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/04.Kafka\351\233\206\347\276\244.md" index adcc18760f..4f0b635fca 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/04.Kafka\351\233\206\347\276\244.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/04.Kafka\351\233\206\347\276\244.md" @@ -1,6 +1,7 @@ --- title: Kafka 集群 date: 2021-04-29 08:17:17 +order: 04 categories: - 分布式 - 分布式通信 @@ -24,7 +25,7 @@ permalink: /pages/fc8f54/ 在 Broker 停机、出现网络分区或长时间垃圾回收停顿时,Broker 会与 ZooKeeper 断开连接,此时 Broker 在启动时创建的临时节点会自动被 ZooKeeper 移除。监听 Broker 列表的 Kafka 组件会被告知 Broker 已移除。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210423171607.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210423171607.png) Kafka 在 ZooKeeper 的关键存储信息: @@ -47,7 +48,7 @@ Kafka 在 ZooKeeper 的关键存储信息: 控制器(Controller),是 Apache Kafka 的核心组件。它的主要作用是在 ZooKeeper 的帮助下管理和协调整个 Kafka 集群。控制器其实就是一个 Broker,只不过它除了具有一般 Broker 的功能以外,还负责 Leader 的选举。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210429071042.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210429071042.png) ### 如何选举控制器 @@ -55,7 +56,7 @@ Kafka 在 ZooKeeper 的关键存储信息: 选举控制器的详细流程: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210502213820.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210502213820.png) 1. 第一个在 ZooKeeper 中成功创建 `/controller` 临时节点的 Broker 会被指定为控制器。 @@ -121,7 +122,7 @@ Preferred 领导者选举主要是 Kafka 为了避免部分 Broker 负载过重 Kafka 使用 Topic 来组织数据,每个 Topic 被分为若干个 Partition,每个 Partition 有多个副本。每个 Broker 可以保存成百上千个属于不同 Topic 和 Partition 的副本。**Kafka 副本的本质是一个只能追加写入的提交日志**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210407180101.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210407180101.png) Kafka 副本有两种角色: @@ -129,7 +130,7 @@ Kafka 副本有两种角色: - **Follower 副本(从)**:Leader 副本以外的副本都是 Follower 副本。**Follower 唯一的任务就是从 Leader 那里复制消息,保持与 Leader 一致的状态**。 - 如果 Leader 宕机,其中一个 Follower 会被选举为新的 Leader。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210407191337.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210407191337.png) 为了与 Leader 保持同步,Follower 向 Leader 发起获取数据的请求,这种请求与消费者为了读取消息而发送的请求是一样的。请求消息里包含了 Follower 想要获取消息的偏移量,而这些偏移量总是有序的。 @@ -165,7 +166,7 @@ broker 会在它所监听的每一个端口上运行一个 Acceptor 线程,这 当请求放进请求队列后,IO 线程负责进行处理。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/10427194506.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/10427194506.png) 生产请求和获取请求都需要发送给 Partition 的 Leader 副本处理。如果 Broker 收到一个针对特定分区的请求,而该分区的 Leader 在另一个 Broker 上,那么发送请求的客户端会收到一个“非分区 Leader”的错误响应。Kafka 客户端要自己负责把生成请求和获取请求发送到正确的 Broker 上。 @@ -175,7 +176,7 @@ broker 会在它所监听的每一个端口上运行一个 Acceptor 线程,这 客户端会把这些信息缓存起来,并直接往目标 Broker 上发送生产请求和获取请求。它们需要时不时地通过发送元数据请求来刷新这些信息(刷新的时间间隔通过 `metadata.max.age.ms` 来配置),从而知道元数据是否发生了变化。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621123848.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621123848.png) ### 生产请求 @@ -203,7 +204,7 @@ Leader 处理拉取请求和处理生产请求的方式很相似: **客户端可以指定 Broker 返回数据量的上限和下限,防止数据量过大造成客户端内存溢出**。同时,**客户端也可以指定返回的最小数据量**,当消息数据量没有达到最小数据量时,请求会一直阻塞直到有足够的数据返回。指定最小的数据量在负载不高的情况下非常有用,通过这种方式**可以减轻网络往返的额外开销**。当然请求也不能永远的阻塞,客户端可以指定最大的阻塞时间,如果到达指定的阻塞时间,即便没有足够的数据也会返回。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621124516.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621124516.png) 不是所有 Leader 的数据都能够被读取。**消费者只能读取已提交的消息**。**只有当消息被写入分区的若干同步副本时,才被认为是已提交的**。为什么是若干个 Broker 呢?这取决于你对“已提交”的定义。你可以选择只要 Leader 成功保存该消息就算是已提交,也可以是令所有 Broker 都成功保存该消息才算是已提交。 @@ -211,7 +212,7 @@ Leader 处理拉取请求和处理生产请求的方式很相似: 这也意味着,如果 Broker 间的消息复制因为某些原因变慢了,那么消息到达消费者的时间也会随之变长。延迟时间可以通过 `replica.lag.time.max.ms` 来配置,它指定了副本在复制消息时可被允许的最大延迟时间。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621124533.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621124533.png) ### 其他请求 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/05.Kafka\345\217\257\351\235\240\344\274\240\350\276\223.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/05.Kafka\345\217\257\351\235\240\344\274\240\350\276\223.md" index 107c517c15..89ed302433 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/05.Kafka\345\217\257\351\235\240\344\274\240\350\276\223.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/05.Kafka\345\217\257\351\235\240\344\274\240\350\276\223.md" @@ -1,6 +1,7 @@ --- title: Kafka 可靠传输 date: 2021-04-14 15:05:34 +order: 05 categories: - 分布式 - 分布式通信 @@ -20,7 +21,7 @@ permalink: /pages/481bdd/ 一条消息从生产到消费,可以划分三个阶段: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210422042613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210422042613.png) - **生产阶段**:Producer 创建消息,并通过网络发送给 Broker。 - **存储阶段**:Broker 收到消息并存储,如果是集群,还要同步副本给其他 Broker。 @@ -76,7 +77,7 @@ Broker 有 3 个配置参数会影响 Kafka 消息存储的可靠性。 在生产消息阶段,消息队列一般通过请求确认机制,来保证消息的可靠传递,Kafka 也不例外。 -[Kafka 生产者](kafka/Kafka生产者.md) 中提到了,Kafka 有三种发送方式:同步、异步、异步回调。 +[Kafka 生产者](02.Kafka生产者.md) 中提到了,Kafka 有三种发送方式:同步、异步、异步回调。 同步方式能保证消息不丢失,但性能太差;异步方式发送消息,通常会立即返回,但消息可能丢失。 @@ -124,7 +125,7 @@ Broker 有 3 个配置参数会影响 Kafka 消息存储的可靠性。 消费者唯一要做的是确保哪些消息是已经读取过的,哪些是没有读取过的(通过提交偏移量给 Broker 来确认)。如果消费者提交了偏移量却未能处理完消息,那么就有可能造成消息丢失,这也是消费者丢失消息的主要原因。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200727140159.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200727140159.png) #### 消费者的可靠性配置 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/06.Kafka\345\255\230\345\202\250.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/06.Kafka\345\255\230\345\202\250.md" index 0e688f0047..c73ca85bae 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/06.Kafka\345\255\230\345\202\250.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/06.Kafka\345\255\230\345\202\250.md" @@ -1,6 +1,7 @@ --- title: Kafka 存储 date: 2021-04-29 08:17:17 +order: 06 categories: - 分布式 - 分布式通信 @@ -20,7 +21,7 @@ permalink: /pages/8de948/ ## 逻辑存储 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210427195053.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210427195053.png) ## 持久化 @@ -55,7 +56,7 @@ Partiton 命名规则为 Topic 名称 + 有序序号,第一个 Partiton 序号 ### Log Segment -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210615200304.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210615200304.png) 因为在一个大文件中查找和删除消息是非常耗时且容易出错的。所以,Kafka 将每个 Partition 切割成若干个片段,即日志段(Log Segment)。**默认每个 Segment 大小不超过 1G,且只包含 7 天的数据**。如果 Segment 的消息量达到 1G,那么该 Segment 会关闭,同时打开一个新的 Segment 进行写入。 @@ -79,7 +80,7 @@ Kafka 的消息和偏移量保存在文件里。保存在磁盘上的数据格 如果生产者发送的是压缩的消息,那么批量发送的消息会压缩在一起,以“包装消息”(wrapper message)来发送,如下所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621134404.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621134404.png) 如果生产者使用了压缩功能,发送的批次越大,就意味着能获得更好的网络传输效率,并且节省磁盘存储空间。 @@ -95,7 +96,7 @@ Kafka 允许消费者从任意有效的偏移量位置开始读取消息。Kafka 有了偏移量索引文件,通过它,Kafka 就能够根据指定的偏移量快速定位到消息的实际物理位置。具体的做法是,根据指定的偏移量,使用二分法查询定位出该偏移量对应的消息所在的分段索引文件和日志数据文件。然后通过二分查找法,继续查找出小于等于指定偏移量的最大偏移量,同时也得出了对应的 position(实际物理位置),根据该物理位置在分段的日志数据文件中顺序扫描查找偏移量与指定偏移量相等的消息。下面是 Kafka 中分段的日志数据文件和偏移量索引文件的对应映射关系图(其中也说明了如何按照起始偏移量来定位到日志数据文件中的具体消息)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210615222550.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210615222550.png) ## 清理 @@ -104,7 +105,7 @@ Kafka 允许消费者从任意有效的偏移量位置开始读取消息。Kafka - **干净的部分**:这部分消息之前已经被清理过,每个键只存在一个值。 - **污浊的部分**:在上一次清理后写入的新消息。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621135557.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621135557.png) 如果在 Kafka 启动时启用了清理功能(通过 `log.cleaner.enabled` 配置),每个 Broker 会启动一个清理管理器线程和若干个清理线程,每个线程负责一个 Partition。 @@ -116,7 +117,7 @@ Kafka 允许消费者从任意有效的偏移量位置开始读取消息。Kafka 对于一个段,清理前后的效果如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200621140117.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200621140117.png) ## 删除事件 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/07.Kafka\346\265\201\345\274\217\345\244\204\347\220\206.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/07.Kafka\346\265\201\345\274\217\345\244\204\347\220\206.md" index 750c44a5b1..58dc648b63 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/07.Kafka\346\265\201\345\274\217\345\244\204\347\220\206.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/07.Kafka\346\265\201\345\274\217\345\244\204\347\220\206.md" @@ -1,6 +1,7 @@ --- title: Kafka 流式处理 date: 2020-07-24 06:52:07 +order: 07 categories: - 分布式 - 分布式通信 @@ -128,7 +129,7 @@ permalink: /pages/55f66f/ 每个流式应用程序至少会实现和执行一个拓扑。拓扑(在其他流式处理框架里叫作 DAG,即有向无环图)是一个操作和变换的集合,每个事件从输入到输出都会流经它。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200622112309.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200622112309.png) ### 分区和任务 @@ -136,7 +137,7 @@ Kafka 的消息传递层对数据进行分区以进行存储和传输。 Kafka S 每个流分区都是数据记录的完全有序序列,并映射到 Kafka 主题分区。流中的数据记录映射到该主题的 Kafka 消息。更具体地说,Kafka Streams 根据应用程序的输入流分区创建固定数量的任务,每个任务分配了输入流中的分区列表(即 Kafka 主题)。分区对任务的分配永远不会改变,因此每个任务都是应用程序并行性的固定单元。然后,任务可以根据分配的分区实例化其自己的处理器拓扑。它们还为其分配的每个分区维护一个缓冲区,并一次从这些记录缓冲区处理消息。结果,可以在没有人工干预的情况下独立且并行地处理流任务。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200622113822.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200622113822.png) ## 参考资料 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/08.Kafka\350\277\220\347\273\264.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/08.Kafka\350\277\220\347\273\264.md" index f544362147..e885b43f2d 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/08.Kafka\350\277\220\347\273\264.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/08.Kafka\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: Kafka 运维 date: 2020-06-03 09:55:35 +order: 08 categories: - 分布式 - 分布式通信 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/README.md" index 9054d2fe48..389f56fe17 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/01.Kafka/README.md" @@ -11,6 +11,7 @@ tags: - Kafka permalink: /pages/328f1c/ hidden: true +index: false --- # Kafka 教程 @@ -60,4 +61,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/01.RocketMQ\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/01.RocketMQ\345\277\253\351\200\237\345\205\245\351\227\250.md" index 94af050782..15a81e8a02 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/01.RocketMQ\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/01.RocketMQ\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: RocketMQ 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - 分布式 - 分布式通信 @@ -22,7 +23,7 @@ RocketMQ 由阿里巴巴孵化,被捐赠给 Apache,成为 Apache 的顶级 ## RocketMQ 概念 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/mq/rocketmq/rmq-model.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/mq/rocketmq/rmq-model.png) ### 消息模型(Message Model) @@ -198,7 +199,7 @@ RocketMQ 将这种正常情况下无法被消费的消息称为死信消息(De ## RocketMQ 组件 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712060550.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712060550.png) RocketMQ 由四部分组成:NameServer、Broker、Producer、Consumer。其中任意一个组成都可以水平扩展为集群模式,以避免单点故障问题。 @@ -243,7 +244,7 @@ Broker 同时支持推拉模型,包含容错机制(2 副本或 3 副本) - **HA Service**:高可用服务,提供 Master Broker 和 Slave Broker 之间的数据同步功能。 - **Index Service**:根据特定的 Message key 对投递到 Broker 的消息进行索引服务,以提供消息的快速查询。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/mq/rocketmq/rmq-basic-component.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/mq/rocketmq/rmq-basic-component.png) ### Producer(生产者) diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/02.RocketMQ\345\237\272\346\234\254\345\216\237\347\220\206.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/02.RocketMQ\345\237\272\346\234\254\345\216\237\347\220\206.md" index 5724d41172..0c8923633f 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/02.RocketMQ\345\237\272\346\234\254\345\216\237\347\220\206.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/02.RocketMQ\345\237\272\346\234\254\345\216\237\347\220\206.md" @@ -1,6 +1,7 @@ --- title: RocketMQ 基本原理 date: 2022-07-08 19:02:04 +order: 02 categories: - 分布式 - 分布式通信 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/99.RocketMQFaq.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/99.RocketMQFaq.md" index b94a9616ce..0019fa0e9f 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/99.RocketMQFaq.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/99.RocketMQFaq.md" @@ -1,6 +1,7 @@ --- title: RocketMQ FAQ date: 2022-07-12 07:49:48 +order: 99 categories: - 分布式 - 分布式通信 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/README.md" index d9160686f9..df28820712 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/02.RocketMQ/README.md" @@ -10,6 +10,7 @@ tags: - MQ permalink: /pages/13dc3a/ hidden: true +index: false --- # RocketMQ @@ -28,4 +29,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/99.\345\205\266\344\273\226MQ/01.ActiveMQ.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/99.\345\205\266\344\273\226MQ/01.ActiveMQ.md" index 154fb8a0a8..ff676edf64 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/99.\345\205\266\344\273\226MQ/01.ActiveMQ.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/99.\345\205\266\344\273\226MQ/01.ActiveMQ.md" @@ -1,6 +1,7 @@ --- title: ActiveMQ 快速入门 date: 2022-02-17 22:34:30 +order: 01 categories: - 分布式 - 分布式通信 @@ -29,7 +30,7 @@ JMS 有两种消息模型: #### P2P 的特点 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/jms/jms-pointToPoint.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/jms/jms-pointToPoint.gif) 在点对点的消息系统中,消息分发给一个单独的使用者。点对点消息往往与队列 `javax.jms.Queue` 相关联。 @@ -43,7 +44,7 @@ JMS 有两种消息模型: #### Pub/Sub 的特点 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/jms/jms-publishSubscribe.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/jms/jms-publishSubscribe.gif) 发布/订阅消息系统支持一个事件驱动模型,消息生产者和消费者都参与消息的传递。生产者发布事件,而使用者订阅感兴趣的事件,并使用事件。该类型消息一般与特定的主题 `javax.jms.Topic` 关联。 @@ -57,7 +58,7 @@ JMS 有两种消息模型: ### JMS 编程模型 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javalib/jms/jms-publishSubscribe.gif) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javalib/jms/jms-publishSubscribe.gif) #### ConnectionFactory diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/README.md" index 7a2705d3ce..c26c28238e 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.MQ/README.md" @@ -11,6 +11,7 @@ tags: - MQ permalink: /pages/dfe847/ hidden: true +index: false --- # 消息队列 @@ -19,7 +20,7 @@ hidden: true > > 消息队列主要解决应用耦合,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。 > -> 如果想深入学习各种消息队列产品,建议先了解一下 [消息队列基本原理](https://dunwu.github.io/blog/pages/1fd240/) ,有助于理解消息队列特性的实现和设计思路。 +> 如果想深入学习各种消息队列产品,建议先了解一下 [消息队列基本原理](https://dunwu.github.io/waterdrop/pages/1fd240/) ,有助于理解消息队列特性的实现和设计思路。 ## 内容 @@ -78,4 +79,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/README.md" index c1b9e6d23b..6e2e7019da 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/README.md" @@ -9,6 +9,7 @@ tags: - 分布式通信 permalink: /pages/3a28d0/ hidden: true +index: false --- # 分布式通信 @@ -45,16 +46,16 @@ hidden: true #### 其他 MQ - [ActiveMQ](02.MQ/99.其他MQ/01.ActiveMQ.md) -- [RocketMQ](02.MQ/99.其他MQ/02.RocketMQ.md) +- [RocketMQ](02.MQ/02.RocketMQ/README.md) ### 分布式存储 -- [数据缓存](docs/15.分布式/22.分布式存储/01.数据缓存.md) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` -- [读写分离](docs/15.分布式/22.分布式存储/02.读写分离.md) -- [分库分表](docs/15.分布式/22.分布式存储/03.分库分表.md) - 关键词:`分片`、`路由`、`迁移`、`扩容`、`双写`、`聚合` +- [分布式缓存](../22.分布式存储/01.分布式缓存.md) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` +- [读写分离](../22.分布式存储/02.读写分离.md) +- [分库分表](../22.分布式存储/03.分库分表.md) - 关键词:`分片`、`路由`、`迁移`、`扩容`、`双写`、`聚合` ## 📚 资料 ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.\346\225\260\346\215\256\347\274\223\345\255\230.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" similarity index 94% rename from "source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.\346\225\260\346\215\256\347\274\223\345\255\230.md" rename to "source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" index 39915ade33..318e5a3bc1 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.\346\225\260\346\215\256\347\274\223\345\255\230.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" @@ -1,6 +1,7 @@ --- title: 缓存基本原理 date: 2019-06-27 15:36:00 +order: 01 categories: - 分布式 - 分布式存储 @@ -27,7 +28,7 @@ permalink: /pages/fd0aaa/ 显然,这一套流程下来,可能需要消耗大量的计算机资源,并且响应时间也可能很久。如果并发请求量很大的话,可能会进一步加剧这种问题。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220224173150.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220224173150.png) 为了解决以上问题,最直接的方式就是引入缓存。缓存可以作用于请求/响应流程的任意环节,并有效减少后续环节的执行次数,从而大大提升性能。 @@ -129,7 +130,7 @@ permalink: /pages/fd0aaa/ > > 国内网络异常复杂,跨运营商的网络访问会很慢。为了解决跨运营商或各地用户访问问题,可以在重要的城市,部署 CDN 应用。使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559138689425.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559138689425.png) #### CDN 原理 @@ -174,7 +175,7 @@ CDN 的基本原理是广泛采用各种缓存服务器,将这些缓存服务 > **反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。** -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/web/nginx/reverse-proxy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/web/nginx/reverse-proxy.png) #### 反向代理缓存原理 @@ -384,7 +385,7 @@ Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户 不同的分布式缓存功能特性和实现原理方面有很大的差异,因此他们所适应的场景也有所不同。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200709224247.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200709224247.png) 这里选取三个比较出名的分布式缓存(MemCache,Redis,Tair)来作为比较: @@ -408,7 +409,7 @@ Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户 通常,一个大型软件系统的缓存采用多级缓存方案: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/cache/缓存整体架构.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/cache/缓存整体架构.png) 请求过程: @@ -440,7 +441,7 @@ Memcached 服务器之间彼此不通信,它的分布式能力是依赖客户 其应用场景如图所示: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200611141419.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200611141419.png) Redis 用来存储热点数据,如果缓存不命中,则去查询数据库,并更新缓存。 @@ -461,7 +462,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 #### 多级缓存查询 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/cache/多级缓存2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/cache/多级缓存2.png) 多级缓存查询流程如下: @@ -480,7 +481,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 为了解决进程内缓存不一致的问题,设计可以进一步优化: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/cache/多级缓存3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/cache/多级缓存3.png) 通过消息队列的发布、订阅机制,可以通知其他应用节点对进程内缓存进行更新。使用这种方案,即使消息队列服务挂了或不可靠,由于先执行了数据库更新,但进程内缓存过期,刷新缓存时,也能保证数据的最终一致性。 @@ -488,7 +489,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 ### 缓存雪崩 -**缓存雪崩是指缓存不可用或者大量缓存由于超时时间相同在同一时间段失效,大量请求直接访问数据库,数据库压力过大导致系统雪崩**。 +**“缓存雪崩”是指,缓存不可用或者大量缓存由于超时时间相同在同一时间段失效,大量请求直接访问数据库,数据库压力过大导致系统雪崩**。 举例来说,对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 @@ -508,7 +509,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 ### 缓存穿透 -> **缓存穿透是指:查询的数据在数据库中不存在,那么缓存中自然也不存在。所以,应用在缓存中查不到,则会去查询数据库。当这样的请求多了后,数据库的压力就会增大。** +**“缓存穿透”是指,查询的数据在数据库中不存在,那么缓存中自然也不存在。所以,应用在缓存中查不到,则会去查询数据库,当这样的请求多了后,数据库的压力就会增大。** 解决缓存穿透,一般有两种方法: @@ -516,13 +517,13 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 **对于返回为 NULL 的依然缓存,对于抛出异常的返回不进行缓存**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/cache/缓存穿透1.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/cache/缓存穿透1.png) 采用这种手段的会增加我们缓存的维护成本,需要在插入缓存的时候删除这个空缓存,当然我们可以通过设置较短的超时时间来解决这个问题。 (二)过滤不可能存在的数据 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/technology/cache/缓存穿透2.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/technology/cache/缓存穿透2.png) **制定一些规则过滤一些不可能存在的数据**。可以使用布隆过滤器(针对二进制操作的数据结构,所以性能高),比如你的订单 ID 明显是在一个范围 1-1000,如果不是 1-1000 之内的数据那其实可以直接给过滤掉。 @@ -536,7 +537,7 @@ Redis 用来存储热点数据,如果缓存不命中,则去查询数据库 ### 缓存击穿 -缓存击穿是指,**热点数据失效瞬间,大量请求直接访问数据库**。例如,某些 key 是热点数据,访问非常频繁。如果某个 key 失效的瞬间,大量的请求过来,缓存未命中,然后去数据库访问,此时数据库访问量会急剧增加。 +**“缓存击穿”是指,热点缓存数据失效瞬间,大量请求直接访问数据库**。例如,某些 key 是热点数据,访问非常频繁。如果某个 key 失效的瞬间,大量的请求过来,缓存未命中,然后去数据库访问,此时数据库访问量会急剧增加。 为了避免这个问题,我们可以采取下面的两个手段: @@ -607,13 +608,13 @@ Cache Aside 的思路是:**先更新数据库,再删除缓存**。具体来 - **更新**:先更新数据库,再删除缓存。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413101039.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413101039.png) ##### 为什么不能先更新数据库,再更新缓存? **多个并发的写操作可能导致脏数据**:当有多个并发的写请求时,无法保证更新数据库的顺序和更新缓存的顺序一致,从而导致数据库和缓存数据不一致的问题。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413113825.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413113825.png) > 说明:如上图的场景中,两个写线程由于执行顺序,导致数据库中 val = 2,而缓存中 val = 1,数据不一致。 @@ -621,7 +622,7 @@ Cache Aside 的思路是:**先更新数据库,再删除缓存**。具体来 **存在并发读请求和写请求时,可能导致脏数据**。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413113940.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413113940.png) > 说明:如上图的场景中,读线程和写线程并行执行,导致数据库中 val = 2,而缓存中 val = 1,数据不一致。 @@ -629,7 +630,7 @@ Cache Aside 的思路是:**先更新数据库,再删除缓存**。具体来 **存在并发读请求和写请求时,可能导致脏数据**。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413115140.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413115140.png) > 上图中问题发生的概率非常低:因为通常数据库更新操作比内存操作耗时多出几个数量级,最后一步回写缓存速度非常快,通常会在更新数据库之前完成。所以 Cache Aside 模式选择先更新数据库,再删除缓存,而不是先删缓存,再更新数据库。 > @@ -637,7 +638,7 @@ Cache Aside 的思路是:**先更新数据库,再删除缓存**。具体来 #### Read/Write Through -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413101029.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413101029.png) Read Through 的思路是:**查询时更新缓存**。当缓存失效时,缓存服务自己进行加载。 @@ -660,7 +661,7 @@ Write Behind 又叫 Write Back。Write Behind 的思路是:应用更新数据 最后,通过一张思维导图来总结一下本文所述的知识点,帮助大家对缓存有一个系统性的认识。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200710163555.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200710163555.png) ## 参考资料 @@ -672,4 +673,4 @@ Write Behind 又叫 Write Back。Write Behind 的思路是:应用更新数据 - [分布式之数据库和缓存双写一致性方案解析 ](https://www.cnblogs.com/rjzheng/p/9041659.html) - [Cache 的基本原理](https://zhuanlan.zhihu.com/p/102293437) - [5 分钟看懂系列:HTTP 缓存机制详解](https://segmentfault.com/a/1190000021716418) -- [浏览器缓存看这一篇就够了](https://zhuanlan.zhihu.com/p/60950750) \ No newline at end of file +- [浏览器缓存看这一篇就够了](https://zhuanlan.zhihu.com/p/60950750) diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/02.\350\257\273\345\206\231\345\210\206\347\246\273.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/02.\350\257\273\345\206\231\345\210\206\347\246\273.md" index 75d68a9f5d..07b69d0ce5 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/02.\350\257\273\345\206\231\345\210\206\347\246\273.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/02.\350\257\273\345\206\231\345\210\206\347\246\273.md" @@ -1,6 +1,7 @@ --- title: 读写分离基本原理 date: 2022-04-14 11:36:23 +order: 02 categories: - 分布式 - 分布式存储 @@ -29,7 +30,7 @@ permalink: /pages/3faf18/ 读写分离的基本实现是: -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave-proxy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/master-slave-proxy.png) - 数据库服务器搭建主从集群,一主一从、一主多从都可以。 - 数据库主机负责读写操作,从机只负责读操作。 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/03.\345\210\206\345\272\223\345\210\206\350\241\250.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/03.\345\210\206\345\272\223\345\210\206\350\241\250.md" index bd714d81b3..30cfa72a84 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/03.\345\210\206\345\272\223\345\210\206\350\241\250.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/03.\345\210\206\345\272\223\345\210\206\350\241\250.md" @@ -1,6 +1,7 @@ --- title: 分库分表基本原理 date: 2019-10-16 20:54:00 +order: 03 categories: - 分布式 - 分布式存储 @@ -49,7 +50,7 @@ permalink: /pages/e1046e/ > 访问频率拆分,是 **把一个有很多字段的表给拆分成多个表,或者是多个库上去**。一般来说,会 **将较少的、访问频率较高的字段放到一个表中**,然后 **将较多的、访问频率较低的字段放到另外一个表中**。因为数据库是有缓存的,访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。 -![image-20200114211639899](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211639899.png) +![image-20200114211639899](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20200114211639899.png) 一般来说,满足下面的条件就可以考虑扩容了: @@ -66,7 +67,7 @@ permalink: /pages/e1046e/ 水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案。 -![image-20200114211203589](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20200114211203589.png) +![image-20200114211203589](https://raw.githubusercontent.com/dunwu/images/master/snap/image-20200114211203589.png) 一般来说,**单表有 200 万条数据** 的时候,性能就会相对差一些了,需要考虑分表了。但是,这也要视具体情况而定,可能是 100 万条,也可能是 500 万条,SQL 越复杂,就最好让单表行数越少。 @@ -74,7 +75,7 @@ permalink: /pages/e1046e/ ### 2.3. 分库分表策略 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200608091832.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200608091832.png) 分库分表策略主要有两种: @@ -115,7 +116,7 @@ permalink: /pages/e1046e/ 停机迁移/扩容是最暴力、最简单的迁移、扩容方案。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601114836.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601114836.png) #### 3.1.1. 停机迁移/扩容流程 @@ -156,7 +157,7 @@ permalink: /pages/e1046e/ 开启双写后,还需要至少稳定运行至少几周的时间,并且期间我们要不断地检查,确保不能有旧库写成功,新库写失败的情况出现。对比程序也没有发现新旧两个库的数据有不一致的情况,这个时候,我们就可以认为,新旧两个库的数据是一直保持同步的。接下来就可以用类似灰度发布的方式,把读请求一点儿一点儿地切到新库上。同样,期间如果出问题的话,可以再切回旧库。全部读请求都切换到新库上之后,这个时候其实读写请求就已经都切换到新库上了,实际的切换已经完成了,但还有后续的收尾步骤。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601135751.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601135751.png) #### 3.2.1. 双写迁移流程 @@ -176,11 +177,11 @@ permalink: /pages/e1046e/ 生产环境的数据库,为了保证高可用,一般会采用主备架构。主库支持读写操作,从库支持读操作。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601121215.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601121215.png) 由于主备节点数据一致,所以将从库升级为主节点,并修改分片配置,将从节点作为分库之一,就实现了扩容。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601121400.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601121400.png) #### 3.3.1. 升级从库的流程 @@ -204,13 +205,13 @@ permalink: /pages/e1046e/ 一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的 ID 无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得 ID,以便进行 SQL 路由。 -> 分布式 ID 的解决方案详见:[分布式 ID](https://dunwu.github.io/blog/pages/3ae455/) +> 分布式 ID 的解决方案详见:[分布式 ID](https://dunwu.github.io/waterdrop/pages/3ae455/) ### 4.2. 分布式事务问题 跨库事务也是分布式的数据库集群要面对的棘手事情。 合理采用分表,可以在降低单表数据量的情况下,尽量使用本地事务,善于使用同库不同表可有效避免分布式事务带来的麻烦。在不能避免跨库事务的场景,有些业务仍然需要保持事务的一致性。 而基于 XA 的分布式事务由于在并发度高的场景中性能无法满足需要,并未被互联网巨头大规模使用,他们大多采用最终一致性的柔性事务代替强一致事务。 -> 分布式事务的解决方案详见:[分布式事务](https://dunwu.github.io/blog/pages/e1881c/) +> 分布式事务的解决方案详见:[分布式事务](https://dunwu.github.io/waterdrop/pages/e1881c/) ### 4.3. 跨节点 Join 和聚合 diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/README.md" index a0632f3e09..ec0da793f6 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/README.md" @@ -9,13 +9,14 @@ tags: - 分布式通信 permalink: /pages/42beb6/ hidden: true +index: false --- # 分布式存储 ## 📖 内容 -- [数据缓存](01.数据缓存.md) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` +- [分布式缓存](01.分布式缓存.md) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` - [读写分离](02.读写分离.md) - [分库分表](03.分库分表.md) - 关键词:`分片`、`路由`、`迁移`、`扩容`、`双写`、`聚合` @@ -23,4 +24,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ diff --git "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/README.md" "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/README.md" index ea2e3c70b5..5bfec68da1 100644 --- "a/source/_posts/15.\345\210\206\345\270\203\345\274\217/README.md" +++ "b/source/_posts/15.\345\210\206\345\270\203\345\274\217/README.md" @@ -7,6 +7,7 @@ tags: - 分布式 permalink: /pages/f21e8c/ hidden: true +index: false --- # 分布式 @@ -99,7 +100,7 @@ hidden: true ### 分布式存储 -- [数据缓存](22.分布式存储/01.数据缓存.md) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` +- [分布式缓存](22.分布式存储/01.分布式缓存.md) - 关键词:`进程内缓存`、`分布式缓存`、`缓存雪崩`、`缓存穿透`、`缓存击穿`、`缓存更新`、`缓存预热`、`缓存降级` - [读写分离](22.分布式存储/02.读写分离.md) - [分库分表](22.分布式存储/03.分库分表.md) - 关键词:`分片`、`路由`、`迁移`、`扩容`、`双写`、`聚合` @@ -109,35 +110,56 @@ hidden: true #### 分布式理论综合资料 -- [The Google File System](https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf):Google 三大经典论文之一 -- [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf):Google 三大经典论文之一 -- [MapReduce: Simplifed Data Processing on Large Clusters](https://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf):Google 三大经典论文之一 -- [分布式系统原理与范型](https://book.douban.com/subject/11691266/):书原名 Distributed Systems Principles and Paradigms。经典分布式教程,介绍了分布式系统的七大核心原理,并给出了大量的例子;系统讲述了分布式系统的概念和技术,包括通信、进程、命名、同步化、一致性和复制、容错以及安全等。 -- [The fallacies of distributed computing](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing) -- [Distributed Systems for fun and profit](http://book.mixu.net/distsys/single-page.html):全书分为五章,讲述了扩展性、可用性、性能和容错等基础知识,FLP 不可能性和 CAP 定理,探讨了大量的一致性模型;讨论了时间和顺序,及时钟的各种用法。随后,探讨了复制问题,如何防止差异,以及如何接受差异。此外,每章末尾都给出了针对本章内容的扩展阅读资源列表,这些资料是对本书内容的很好补充。 -- [分布式技术原理与算法解析](https://time.geekbang.org/column/intro/100036401) - 极客时间教程 -- [分布式协议与算法实战](https://time.geekbang.org/column/intro/100046101) - 极客时间教程 - -#### Paxos 资料 - -- [Part-time Parliament 论文](https://research.microsoft.com/en-us/um/people/lamport/pubs/lamport-paxos.pdf) - Lamport 的 Paxos 论文。这篇论文很权威,但较为晦涩难懂。 -- [Paxos Made Simple 论文](https://lamport.azurewebsites.net/pubs/paxos-simple.pdf) -- [Paxos 算法详解](https://zhuanlan.zhihu.com/p/31780743) -- Neat Algorithms - Paxos -- [Wiki - Paxos 算法](https://zh.wikipedia.org/w/index.php?title=Paxos%E7%AE%97%E6%B3%95) -- [一致性算法(Paxos、Raft、Zab)](https://www.bilibili.com/video/BV1TW411M7Fx?from=search&seid=11524608198747599965) -- [Raft 作者讲解 Paxos 视频](https://www.bilibili.com/video/av36556594) -- [Paxos 算法讲解视频](https://www.youtube.com/watch?v=d7nAGI_NZPk) - -#### Raft 资料 - -- [Raft 算法论文原文](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) -- [Raft 算法论文译文](https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md) -- [Raft 作者讲解视频](https://www.youtube.com/watch?v=YbZ3zDzDnrw&feature=youtu.be) -- [Raft 作者讲解视频对应的 PPT](http://www2.cs.uh.edu/~paris/6360/PowerPoint/Raft.ppt) -- [Raft 算法详解](https://zhuanlan.zhihu.com/p/32052223) -- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) - 一个动画教程 -- [The Raft Consensus Algorithm](https://raft.github.io/) - 一个交互式动画教程 +- **教程** + - [分布式技术原理与算法解析](https://time.geekbang.org/column/intro/100036401) - 极客时间教程 + - [分布式协议与算法实战](https://time.geekbang.org/column/intro/100046101) - 极客时间教程 + - [Distributed Systems for fun and profit](http://book.mixu.net/distsys/single-page.html):分为五章,讲述了扩展性、可用性、性能和容错等基础知识,FLP 不可能性和 CAP 定理,探讨了大量的一致性模型;讨论了时间和顺序,及时钟的各种用法。随后,探讨了复制问题,如何防止差异,以及如何接受差异。此外,每章末尾都给出了针对本章内容的扩展阅读资源列表,这些资料是对本书内容的很好补充。 +- **书籍** + - [分布式系统原理与范型](https://book.douban.com/subject/11691266/):书原名 Distributed Systems Principles and Paradigms。经典分布式教程,介绍了分布式系统的七大核心原理,并给出了大量的例子;系统讲述了分布式系统的概念和技术,包括通信、进程、命名、同步化、一致性和复制、容错以及安全等。 +- **文章** + - [The Google File System](https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf):Google 三大经典论文之一 + - [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf):Google 三大经典论文之一 + - [MapReduce: Simplifed Data Processing on Large Clusters](https://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf):Google 三大经典论文之一 + - [Time, Clocks, and the Ordering of Events in a Distributed System](https://lamport.azurewebsites.net/pubs/time-clocks.pdf) + - [The Byzantine Generals Problem](https://lamport.azurewebsites.net/pubs/byz.pdf) + - [Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services](https://www.comp.nus.edu.sg/~gilbert/pubs/BrewersConjecture-SigAct.pdf) - CAP 论文 + - CAP Twelve Years Later: How the “Rules” Have Changed + - BASE: An Acid Alternative + - A Simple Totally Ordered Broadcast Protocol + - Virtual Time and Global States of Distributed Systems + - [The fallacies of distributed computing](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing) + +#### 分布式一致性算法 + +- **教程** + - [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) - 一个动画教程 + - [The Raft Consensus Algorithm](https://raft.github.io/) - 一个交互式动画教程 +- **视频** + - [Raft 作者讲解 Paxos 视频](https://www.bilibili.com/video/av36556594) + - [Paxos 算法讲解视频](https://www.youtube.com/watch?v=d7nAGI_NZPk) + - [Raft 作者讲解视频](https://www.youtube.com/watch?v=YbZ3zDzDnrw&feature=youtu.be) + - [Raft 作者讲解视频对应的 PPT](http://www2.cs.uh.edu/~paris/6360/PowerPoint/Raft.ppt) +- 文章 + + - [Part-time Parliament 论文](https://research.microsoft.com/en-us/um/people/lamport/pubs/lamport-paxos.pdf) + - [Paxos Made Simple 论文](https://lamport.azurewebsites.net/pubs/paxos-simple.pdf) + - Paxos Made Practical + - Paxos Made Live: An Engineering Perspective + - Using Paxos to Build a Scalable, Consistent, and Highly Available Datastore + Impossibility of Distributed Consensus With One Faulty Process + - [Paxos 算法详解](https://zhuanlan.zhihu.com/p/31780743) + - [一致性算法(Paxos、Raft、Zab)](https://www.bilibili.com/video/BV1TW411M7Fx?from=search&seid=11524608198747599965) + - [分布式协议与算法实战](https://time.geekbang.org/column/intro/100046101) + + - [Raft: In Search of an Understandable Consensus Algorithm](https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf) + - [Raft 算法论文译文](https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md) + - [分布式系统的 Raft 算法](https://www.jdon.com/artichect/raft.html) + - [Raft 算法详解](https://zhuanlan.zhihu.com/p/32052223) + - A Brief History of Consensus, 2PC and Transaction Commit + - Consensus in the Presence of Partial Synchrony + +- 工具 + - [sofa-jraft](https://github.com/sofastack/sofa-jraft) - 蚂蚁金服的 Raft 算法实现库(Java 版) #### Goosip 资料 @@ -158,15 +180,16 @@ hidden: true #### MQ 资料 -##### MQ 综合资料 - - **教程** - [消息队列高手课](https://time.geekbang.org/column/intro/100032301) + - [Kafka 中文文档](https://github.com/apachecn/kafka-doc-zh) + - [Kafka 核心技术与实战](https://time.geekbang.org/column/intro/100029201) + - [Kafka 核心源码解读](https://time.geekbang.org/column/intro/304) - **文章** + - [The Log: What every software engineer should know about real-time data’s unifying abstraction](https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying) - [《日志:每个软件工程师都应该知道的有关实时数据的统一抽象》](https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying) - 上面文章的译文 - -##### Kafka 资料 + - [Introduction and Overview of Apache Kafka](https://www.slideshare.net/mumrah/kafka-talk-tri-hug) - **官方** - [Kafka 官网](http://kafka.apache.org/) @@ -178,17 +201,44 @@ hidden: true - [《Kafka 权威指南》](https://item.jd.com/12270295.html) - [《深入理解 Kafka:核心设计与实践原理》](https://item.jd.com/12489649.html) - [《Kafka 技术内幕》](https://item.jd.com/12234113.html) -- **教程** - - [Kafka 中文文档](https://github.com/apachecn/kafka-doc-zh) - - [Kafka 核心技术与实战](https://time.geekbang.org/column/intro/100029201) - - [Kafka 核心源码解读](https://time.geekbang.org/column/intro/304) -- **文章** - - [Introduction and Overview of Apache Kafka](https://www.slideshare.net/mumrah/kafka-talk-tri-hug) ### 分布式存储资料 -- [《数据密集型应用系统设计》](https://book.douban.com/subject/30329536/) - 这可能是目前最好的分布式存储书籍,强力推荐【进阶】 +- **书籍** + - [《数据密集型应用系统设计》](https://book.douban.com/subject/30329536/) - 这可能是目前最好的分布式存储书籍,强力推荐【进阶】 +- **文章** + - Chord: A Scalable Peer-to-Peer Lookup Service for Internet Applications + - Pastry: Scalable, Distributed Object Location, and Routing for Large-Scale Peerto-Peer Systems + - Kademlia: A Peer-to-Peer Information System Based on the XOR Metric + - A Scalable Content-Addressable Network + - Ceph: A Scalable, High-Performance Distributed File System + - [The Log-Structured-Merge-Tree](chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/https://www.cs.umb.edu/~poneil/lsmtree.pdf) + - [HBase: A NoSQL Database](https://www.researchgate.net/publication/317399857_HBase_A_NoSQL_Database) + - Tango: Distributed Data Structure over a Shared Log + +### 分布式系统实战 + +- The Google File System +- BigTable: A Distributed Storage System for Structured Data +- The Chubby Lock Service for Loosely-Coupled Distributed Systems +- Finding a Needle in Haystack: Facebook’s Photo Storage +- Windows Azure Storage: A Highly Available Cloud Storage Service with Strong Consistency +- Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing +- Scaling Distributed Machine Learning with the Parameter Server +- Dremel: Interactive Analysis of Web-Scale Datasets +- Pregel: A System for Large-Scale Graph Processing +- Spanner: Google’s Globally-Distributed Database +- Dynamo: Amazon’s Highly Available Key-value Store +- S4: Distributed Stream Computing Platform +- Storm @Twitter +- Large-scale Cluster Management at Google with Borg +- F1 - The Fault-Tolerant Distributed RDBMS Supporting Google’s Ad Business +- Cassandra: A Decentralized Structured Storage System +- MegaStore: Providing Scalable, Highly Available Storage for Interactive Services +- Dapper, a Large-Scale Distributed Systems Tracing Infrastructure +- Kafka: A distributed Messaging System for Log Processing +- Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/01.\345\244\247\346\225\260\346\215\256\347\256\200\344\273\213.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/01.\345\244\247\346\225\260\346\215\256\347\256\200\344\273\213.md" index 860b0968e7..6000806806 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/01.\345\244\247\346\225\260\346\215\256\347\256\200\344\273\213.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/01.\345\244\247\346\225\260\346\215\256\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: 大数据简介 date: 2019-05-07 20:19:25 +order: 01 categories: - 大数据 - 综合 diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/02.\345\244\247\346\225\260\346\215\256\345\255\246\344\271\240.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/02.\345\244\247\346\225\260\346\215\256\345\255\246\344\271\240.md" index 1b15505222..d04d0a3919 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/02.\345\244\247\346\225\260\346\215\256\345\255\246\344\271\240.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/02.\345\244\247\346\225\260\346\215\256\345\255\246\344\271\240.md" @@ -1,6 +1,7 @@ --- title: 大数据学习 date: 2020-06-22 00:22:25 +order: 02 categories: - 大数据 - 综合 @@ -39,7 +40,7 @@ permalink: /pages/e0d035/ ## 大数据处理流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220217114216.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220217114216.png) ### 1.1 数据采集 @@ -106,7 +107,7 @@ permalink: /pages/e0d035/ 上面列出的都是比较主流的大数据框架,社区都很活跃,学习资源也比较丰富。建议从 Hadoop 开始入门学习,因为它是整个大数据生态圈的基石,其它框架都直接或者间接依赖于 Hadoop 。接着就可以学习计算框架,Spark 和 Flink 都是比较主流的混合处理框架,Spark 出现得较早,所以其应用也比较广泛。 Flink 是当下最火热的新一代的混合处理框架,其凭借众多优异的特性得到了众多公司的青睐。两者可以按照你个人喜好或者实际工作需要进行学习。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601160917.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601160917.png) ### 学习资料 diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/README.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/README.md" index 38fd87d418..86fb6832c4 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/README.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/00.\347\273\274\345\220\210/README.md" @@ -9,6 +9,7 @@ tags: - 综合 permalink: /pages/ad9b6a/ hidden: true +index: false --- # 大数据综合 diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/01.HDFS\345\205\245\351\227\250.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/01.HDFS\345\205\245\351\227\250.md" index 1760d9d450..222fc4b58d 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/01.HDFS\345\205\245\351\227\250.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/01.HDFS\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: HDFS 入门 date: 2020-02-24 21:14:47 +order: 01 categories: - 大数据 - hadoop @@ -51,7 +52,7 @@ HDFS 是在一个大规模分布式服务器集群上,对数据分片后进行 集群中的 Datanode 一般是一个节点一个,负责管理它所在节点上的存储。HDFS 暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组 Datanode 上。Namenode 执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体 Datanode 节点的映射。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hdfs/hdfs-architecture.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hdfs/hdfs-architecture.png) ### NameNode @@ -94,7 +95,7 @@ HDFS 的 `文件系统命名空间` 的层次结构与大多数文件系统类 ### HDFS 读文件 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hdfs/hdfs-read.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hdfs/hdfs-read.png) 1. 客户端调用 FileSyste 对象的 open() 方法在分布式文件系统中**打开要读取的文件**。 2. 分布式文件系统通过使用 RPC(远程过程调用)来调用 namenode,**确定文件起始块的位置**。 @@ -105,7 +106,7 @@ HDFS 的 `文件系统命名空间` 的层次结构与大多数文件系统类 ### HDFS 写文件 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hdfs/hdfs-write.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hdfs/hdfs-write.png) 1. 客户端通过对 DistributedFileSystem 对象调用 create() 函数来**新建文件**。 2. 分布式文件系统对 namenod 创建一个 RPC 调用,在文件系统的**命名空间中新建一个文件**。 @@ -119,11 +120,11 @@ HDFS 的 `文件系统命名空间` 的层次结构与大多数文件系统类 由于 Hadoop 被设计运行在廉价的机器上,这意味着硬件是不可靠的,为了保证容错性,HDFS 提供了数据复制机制。HDFS 将每一个文件存储为一系列**块**,每个块由多个副本来保证容错,块的大小和复制因子可以自行配置(默认情况下,块大小是 128M,默认复制因子是 3)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224203958.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224203958.png) **Namenode 全权管理数据块的复制**,它周期性地从集群中的每个 Datanode 接收心跳信号和块状态报告(Blockreport)。接收到心跳信号意味着该 Datanode 节点工作正常。块状态报告包含了一个该 Datanode 上所有数据块的列表。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hdfs/hdfs-replica.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hdfs/hdfs-replica.png) 大型的 HDFS 实例在通常分布在多个机架的多台服务器上,不同机架上的两台服务器之间通过交换机进行通讯。在大多数情况下,同一机架中的服务器间的网络带宽大于不同机架中的服务器之间的带宽。因此 HDFS 采用机架感知副本放置策略,对于常见情况,当复制因子为 3 时,HDFS 的放置策略是: diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/02.HDFS\350\277\220\347\273\264.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/02.HDFS\350\277\220\347\273\264.md" index 83439c252b..5f4e16d126 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/02.HDFS\350\277\220\347\273\264.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/02.HDFS\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: HDFS 运维 date: 2020-02-24 21:14:47 +order: 02 categories: - 大数据 - hadoop diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/03.HDFSJavaApi.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/03.HDFSJavaApi.md" index 64e4f221e6..7cbc81b287 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/03.HDFSJavaApi.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/03.HDFSJavaApi.md" @@ -1,6 +1,7 @@ --- title: HDFS Java API date: 2020-02-24 21:14:47 +order: 03 categories: - 大数据 - hadoop diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/README.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/README.md" index e35f76f3ea..a9dd26426c 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/README.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/01.hdfs/README.md" @@ -11,6 +11,7 @@ tags: - HDFS permalink: /pages/8d798e/ hidden: true +index: false --- # HDFS 教程 @@ -41,4 +42,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/02.yarn.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/02.yarn.md" index 3435f3cc58..88d4157ce5 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/02.yarn.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/02.yarn.md" @@ -1,6 +1,7 @@ --- title: YARN date: 2019-05-07 20:19:25 +order: 02 categories: - 大数据 - hadoop diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/03.mapreduce.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/03.mapreduce.md" index a30069bed7..a9ae833f6e 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/03.mapreduce.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/03.mapreduce.md" @@ -1,6 +1,7 @@ --- title: MapReduce date: 2020-06-22 00:22:25 +order: 03 categories: - 大数据 - hadoop @@ -59,7 +60,7 @@ MapReduce 作业通过将输入的数据集拆分为独立的块,这些块由 MapReduce 编程模型:MapReduce 程序被分为 Map(映射)阶段和 Reduce(化简)阶段。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601162305.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601162305.png) 1. **input** : 读取文本文件; 2. **splitting** : 将文件按照行进行拆分,此时得到的 `K1` 行数,`V1` 表示对应行的文本内容; @@ -71,7 +72,7 @@ MapReduce 编程模型中 `splitting` 和 `shuffing` 操作都是由框架实现 ## combiner & partitioner -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601163846.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601163846.png) ### InputFormat & RecordReaders @@ -87,11 +88,11 @@ MapReduce 编程模型中 `splitting` 和 `shuffing` 操作都是由框架实现 不使用 combiner 的情况: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601164709.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601164709.png) 使用 combiner 的情况: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200601164804.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200601164804.png) 可以看到使用 combiner 的时候,需要传输到 reducer 中的数据由 12keys,降低到 10keys。降低的幅度取决于你 keys 的重复率,下文词频统计案例会演示用 combiner 降低数百倍的传输量。 diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/README.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/README.md" index 99093581b4..881e53ecca 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/README.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/01.hadoop/README.md" @@ -9,6 +9,7 @@ tags: - Hadoop permalink: /pages/680e30/ hidden: true +index: false --- # Hadoop 教程 @@ -29,4 +30,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/01.Hive\345\205\245\351\227\250.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/01.Hive\345\205\245\351\227\250.md" index 4fe1003818..1831cb414b 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/01.Hive\345\205\245\351\227\250.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/01.Hive\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Hive 入门 date: 2020-02-24 21:14:47 +order: 01 categories: - 大数据 - hive @@ -26,7 +27,7 @@ Hive 是一个构建在 Hadoop 之上的数据仓库,它可以将结构化的 ## Hive 的体系架构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224193019.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224193019.png) ### command-line shell & thrift/jdbc @@ -79,7 +80,7 @@ Hive 表中的列支持以下基本数据类型: Hive 中基本数据类型遵循以下的层次结构,按照这个层次结构,子类型到祖先类型允许隐式转换。例如 INT 类型的数据允许隐式转换为 BIGINT 类型。额外注意的是:按照类型层次结构允许将 STRING 类型隐式转换为 DOUBLE 类型。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224193613.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224193613.png) ### 复杂类型 diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/02.Hive\350\241\250.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/02.Hive\350\241\250.md" index 26f362a758..3d84d18dd9 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/02.Hive\350\241\250.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/02.Hive\350\241\250.md" @@ -1,6 +1,7 @@ --- title: Hive 分区表和分桶表 date: 2020-02-24 21:14:47 +order: 02 categories: - 大数据 - hive @@ -82,7 +83,7 @@ LOAD DATA LOCAL INPATH "/usr/file/emp30.txt" OVERWRITE INTO TABLE emp_partition 当调用 HashMap 的 put() 方法存储数据时,程序会先对 key 值调用 hashCode() 方法计算出 hashcode,然后对数组长度取模计算出 index,最后将数据存储在数组 index 位置的链表上,链表达到一定阈值后会转换为红黑树 (JDK1.8+)。下图为 HashMap 的数据结构图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224194352.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224194352.png) 图片引用自:[HashMap vs. Hashtable](http://www.itcuties.com/java/hashmap-hashtable/) diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/03.Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/03.Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md" index c27bdec751..c077afc603 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/03.Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/03.Hive\350\247\206\345\233\276\345\222\214\347\264\242\345\274\225.md" @@ -1,6 +1,7 @@ --- title: Hive 视图和索引 date: 2020-02-24 21:14:47 +order: 03 categories: - 大数据 - hive @@ -216,4 +217,4 @@ SHOW INDEX ON emp; - [Create/Drop/Alter View](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-Create/Drop/AlterView) - [Materialized views](https://cwiki.apache.org/confluence/display/Hive/Materialized+views) - [Hive 索引](http://lxw1234.com/archives/2015/05/207.htm) -- [Overview of Hive Indexes](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Indexing) +- [Overview of Hive Indexes](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Indexing) \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/04.Hive\346\237\245\350\257\242.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/04.Hive\346\237\245\350\257\242.md" index 44a755be46..86eb8c747d 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/04.Hive\346\237\245\350\257\242.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/04.Hive\346\237\245\350\257\242.md" @@ -1,6 +1,7 @@ --- title: Hive 数据查询详解 date: 2020-02-24 21:14:47 +order: 04 categories: - 大数据 - hive @@ -209,7 +210,7 @@ Hive 支持内连接,外连接,左外连接,右外连接,笛卡尔连接 需要特别强调:JOIN 语句的关联条件必须用 ON 指定,不能用 WHERE 指定,否则就会先做笛卡尔积,再过滤,这会导致你得不到预期的结果 (下面的演示会有说明)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224195733.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224195733.png) ### INNER JOIN diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/05.HiveDDL.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/05.HiveDDL.md" index 2256368c03..fe86b0a470 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/05.HiveDDL.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/05.HiveDDL.md" @@ -1,6 +1,7 @@ --- title: Hive 常用 DDL 操作 date: 2020-02-24 21:14:47 +order: 05 categories: - 大数据 - hive diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/06.HiveDML.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/06.HiveDML.md" index 249cd08d38..055590be06 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/06.HiveDML.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/06.HiveDML.md" @@ -1,6 +1,7 @@ --- title: Hive 常用 DML 操作 date: 2020-02-24 21:14:47 +order: 06 categories: - 大数据 - hive diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/07.Hive\350\277\220\347\273\264.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/07.Hive\350\277\220\347\273\264.md" index 5b12d6b8c0..edcca3efb5 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/07.Hive\350\277\220\347\273\264.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/07.Hive\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: hive-ops date: 2020-02-24 21:14:47 +order: 07 categories: - 大数据 - hive diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/README.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/README.md" index a37af7bbbc..49f710f600 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/README.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/02.hive/README.md" @@ -9,6 +9,7 @@ tags: - Hive permalink: /pages/a958fe/ hidden: true +index: false --- # Hive 教程 @@ -27,4 +28,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/01.ZooKeeper\345\216\237\347\220\206.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/01.ZooKeeper\345\216\237\347\220\206.md" deleted file mode 100644 index 0d7e35702a..0000000000 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/01.ZooKeeper\345\216\237\347\220\206.md" +++ /dev/null @@ -1,469 +0,0 @@ ---- -title: ZooKeeper 原理 -date: 2020-06-02 22:28:54 -categories: - - 大数据 - - zookeeper -tags: - - 分布式 - - 大数据 - - ZooKeeper -permalink: /pages/f9ff40/ ---- - -# ZooKeeper 原理 - -> ZooKeeper 是 Apache 的顶级项目。**ZooKeeper 为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面,ZooKeeper 并没有直接采用 Paxos 算法,而是采用了名为 ZAB 的一致性协议**。 -> -> ZooKeeper 主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来**维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理**。 -> -> 很多大名鼎鼎的框架都基于 ZooKeeper 来实现分布式高可用,如:Dubbo、Kafka 等。 -> -> ZooKeeper 官方支持 Java 和 C 的 Client API。ZooKeeper 社区为大多数语言(.NET,python 等)提供非官方 API。 - -## ZooKeeper 简介 - -### ZooKeeper 是什么 - -ZooKeeper 是 Apache 的顶级项目。**ZooKeeper 为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面,ZooKeeper 并没有直接采用 Paxos 算法,而是采用了名为 ZAB 的一致性协议**。 - -ZooKeeper 主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来**维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理**。 - -很多大名鼎鼎的框架都基于 ZooKeeper 来实现分布式高可用,如:Dubbo、Kafka 等。 - -### ZooKeeper 的应用场景 - -- 配置管理 - - 集群节点可以通过中心源获取启动配置 - - 更简单的部署 -- 分布式集群管理 - - 节点加入/离开 - - 节点的实时状态 -- 命名服务,如:DNS -- 分布式同步:如锁、栅栏、队列 -- 分布式系统的选主 -- 中心化和高可靠的数据注册 - -### ZooKeeper 的特性 - -ZooKeeper 具有以下特性: - -- **顺序一致性**:所有客户端看到的服务端数据模型都是一致的;从一个客户端发起的事务请求,最终都会严格按照其发起顺序被应用到 ZooKeeper 中。具体的实现可见:[原子广播](#原子广播) -- **原子性** - 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,即整个集群要么都成功应用了某个事务,要么都没有应用。 实现方式可见:[事务](#事务) -- **单一视图** - 无论客户端连接的是哪个 Zookeeper 服务器,其看到的服务端数据模型都是一致的。 -- **高性能** - ZooKeeper 将**数据全量存储在内存中**,所以其性能很高。需要注意的是:由于 **ZooKeeper 的所有更新和删除都是基于事务的**,因此 ZooKeeper 在读多写少的应用场景中有性能表现较好,**如果写操作频繁,性能会大大下滑**。 -- **高可用** - ZooKeeper 的高可用是基于副本机制实现的,此外 ZooKeeper 支持故障恢复,可见:[选举 Leader](#选举-Leader) - -### ZooKeeper 的设计目标 - -- 简单的数据模型:ZooKeeper 的数据模型是一个树形结构的文件系统,树中的节点被称为 **`znode`**。 -- 可以构建集群:ZooKeeper 支持集群模式,可以通过伸缩性,来控制集群的吞吐量。需要注意的是:由于 ZooKeeper 采用一主多从架构,所以其写性能是有上限的,比较适合于读多写少的场景。 -- 顺序访问:对于来自客户端的每个更新请求,Zookeeper 都会分配一个全局唯一的递增 ID,这个 ID 反映了所有事务请求的先后顺序。 -- 高性能、高可用:ZooKeeper 将数据存全量储在内存中以保持高性能,并通过服务集群来实现高可用,由于 Zookeeper 的所有更新和删除都是基于事务的,所以其在读多写少的应用场景中有着很高的性能表现。 - -## ZooKeeper 核心概念 - -### 服务 - -Zookeeper 服务是一个基于主从复制的高可用集群,集群中每个节点都存储了一份数据副本(内存中)。 - -客户端只会连接一个 ZooKeeper 服务器节点,并维持 TCP 连接。 - -### 数据模型 - -**ZooKeeper 的数据模型是一个树形结构的文件系统**。 - -树中的节点被称为 **`znode`**,其中根节点为 `/`,每个节点上都会保存自己的数据和节点信息。znode 可以用于存储数据,并且有一个与之相关联的 ACL(详情可见 [ACL](#ACL))。ZooKeeper 的设计目标是实现协调服务,而不是真的作为一个文件存储,因此 znode 存储数据的**大小被限制在 1MB 以内**。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_1.png) - -**ZooKeeper 的数据访问具有原子性**。其读写操作都是要么全部成功,要么全部失败。 - -znode 通过路径被引用。**znode 节点路径必须是绝对路径**。 - -znode 有两种类型: - -- **临时的( `EPHEMERAL` )** - 户端会话结束时,ZooKeeper 就会删除临时的 znode。不允许有子节点。 -- **持久的(`PERSISTENT` )** - 除非客户端主动执行删除操作,否则 ZooKeeper 不会删除持久的 znode。 - -### 节点信息 - -znode 上有一个**顺序标志( `SEQUENTIAL` )**。如果在创建 znode 时,设置了**顺序标志( `SEQUENTIAL` )**,那么 ZooKeeper 会使用计数器为 znode 添加一个单调递增的数值,即 `zxid`。ZooKeeper 正是利用 zxid 实现了严格的顺序访问控制能力。 - -每个 znode 节点在存储数据的同时,都会维护一个叫做 `Stat` 的数据结构,里面存储了关于该节点的全部状态信息。如下: - -| **状态属性** | **说明** | -| -------------- | ------------------------------------------------------------------------------------------ | -| czxid | 数据节点创建时的事务 ID | -| ctime | 数据节点创建时的时间 | -| mzxid | 数据节点最后一次更新时的事务 ID | -| `mtime` | 数据节点最后一次更新时的时间 | -| pzxid | 数据节点的子节点最后一次被修改时的事务 ID | -| cversion | 子节点的更改次数 | -| version | 节点数据的更改次数 | -| aversion | 节点的 ACL 的更改次数 | -| ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | -| dataLength | 数据内容的长度 | -| numChildren | 数据节点当前的子节点个数 | - -### 集群角色 - -Zookeeper 集群是一个基于主从复制的高可用集群,集群中每个节点都存储了一份数据副本(内存中)。此外,每个服务器节点承担如下三种角色中的一种: - -- **Leader** - 它负责 **发起并维护与各 Follwer 及 Observer 间的心跳。所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器**。一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader。 -- **Follower** - 它会**响应 Leader 的心跳。Follower 可直接处理并返回客户端的读请求,同时会将写请求转发给 Leader 处理,并且负责在 Leader 处理写请求时对请求进行投票**。一个 Zookeeper 集群可能同时存在多个 Follower。 -- **Observer** - 角色与 Follower 类似,但是无投票权。 - -客户端可以从任意 ZooKeeper 服务器节点读取数据,但只能通过 Leader 服务写数据并需要半数以上 Follower 的 ACK,才算写入成功。记住这个重要的知识点,下文会详细讲述。 - -### ACL - -**ZooKeeper 采用 ACL(Access Control Lists)策略来进行权限控制**。 - -每个 znode 创建时都会带有一个 ACL 列表,用于决定谁可以对它执行何种操作。 - -ACL 依赖于 ZooKeeper 的客户端认证机制。ZooKeeper 提供了以下几种认证方式: - -- **digest** - 用户名和密码 来识别客户端 -- **sasl** - 通过 kerberos 来识别客户端 -- **ip** - 通过 IP 来识别客户端 - -ZooKeeper 定义了如下五种权限: - -- **CREATE** - 允许创建子节点; -- **READ** - 允许从节点获取数据并列出其子节点; -- **WRITE** - 允许为节点设置数据; -- **DELETE** - 允许删除子节点; -- **ADMIN** - 允许为节点设置权限。 - -## ZooKeeper 工作原理 - -### 读操作 - -**Leader/Follower/Observer 都可直接处理读请求,从本地内存中读取数据并返回给客户端即可**。 - -由于处理读请求不需要服务器之间的交互,**Follower/Observer 越多,整体系统的读请求吞吐量越大**,也即读性能越好。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) - -### 写操作 - -所有的写请求实际上都要交给 Leader 处理。Leader 将写请求以事务形式发给所有 Follower 并等待 ACK,一旦收到半数以上 Follower 的 ACK,即认为写操作成功。 - -#### 写 Leader - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_4.png) - -由上图可见,通过 Leader 进行写操作,主要分为五步: - -1. 客户端向 Leader 发起写请求 -2. Leader 将写请求以事务 Proposal 的形式发给所有 Follower 并等待 ACK -3. Follower 收到 Leader 的事务 Proposal 后返回 ACK -4. Leader 得到过半数的 ACK(Leader 对自己默认有一个 ACK)后向所有的 Follower 和 Observer 发送 Commmit -5. Leader 将处理结果返回给客户端 - -> 注意 -> -> - Leader 不需要得到 Observer 的 ACK,即 Observer 无投票权。 -> - Leader 不需要得到所有 Follower 的 ACK,只要收到过半的 ACK 即可,同时 Leader 本身对自己有一个 ACK。上图中有 4 个 Follower,只需其中两个返回 ACK 即可,因为 $$(2+1) / (4+1) > 1/2$$ 。 -> - Observer 虽然无投票权,但仍须同步 Leader 的数据从而在处理读请求时可以返回尽可能新的数据。 - -#### 写 Follower/Observer - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_5.png) - -- Follower/Observer 均可接受写请求,但不能直接处理,而需要将写请求转发给 Leader 处理。 -- 除了多了一步请求转发,其它流程与直接写 Leader 无任何区别。 - -### 事务 - -对于来自客户端的每个更新请求,ZooKeeper 具备严格的顺序访问控制能力。 - -**为了保证事务的顺序一致性,ZooKeeper 采用了递增的事务 id 号(zxid)来标识事务**。 - -**Leader 服务会为每一个 Follower 服务器分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略进行消息发送**。Follower 服务在接收到 Proposal 后,会将其以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 Ack 响应。**当 Leader 接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower 以通知其进行事务提交**,之后 Leader 自身也会完成对事务的提交。而每一个 Follower 则在接收到 Commit 消息后,完成事务的提交。 - -所有的提议(**`proposal`**)都在被提出的时候加上了 zxid。zxid 是一个 64 位的数字,它的高 32 位是 **`epoch`** 用来标识 Leader 关系是否改变,每次一个 Leader 被选出来,它都会有一个新的 epoch,标识当前属于那个 leader 的统治时期。低 32 位用于递增计数。 - -详细过程如下: - -1. Leader 等待 Server 连接; -2. Follower 连接 Leader,将最大的 zxid 发送给 Leader; -3. Leader 根据 Follower 的 zxid 确定同步点; -4. 完成同步后通知 follower 已经成为 uptodate 状态; -5. Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了。 - -### 观察 - -**ZooKeeper 允许客户端监听它关心的 znode,当 znode 状态发生变化(数据变化、子节点增减变化)时,ZooKeeper 服务会通知客户端**。 - -客户端和服务端保持连接一般有两种形式: - -- **客户端向服务端不断轮询** -- **服务端向客户端推送状态** - -Zookeeper 的选择是服务端主动推送状态,也就是观察机制( `Watch` )。 - -ZooKeeper 的观察机制允许用户在指定节点上针对感兴趣的事件注册监听,当事件发生时,监听器会被触发,并将事件信息推送到客户端。 - -- 监听器实时触发 -- 监听器总是有序的 -- 创建新的 znode 数据前,客户端就能收到监听事件。 - -客户端使用 `getData` 等接口获取 znode 状态时传入了一个用于处理节点变更的回调,那么服务端就会主动向客户端推送节点的变更: - -```java -public byte[] getData(final String path, Watcher watcher, Stat stat) -``` - -从这个方法中传入的 `Watcher` 对象实现了相应的 `process` 方法,每次对应节点出现了状态的改变,`WatchManager` 都会通过以下的方式调用传入 `Watcher` 的方法: - -```java -Set triggerWatch(String path, EventType type, Set supress) { - WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path); - Set watchers; - synchronized (this) { - watchers = watchTable.remove(path); - } - for (Watcher w : watchers) { - w.process(e); - } - return watchers; -} -``` - -Zookeeper 中的所有数据其实都是由一个名为 `DataTree` 的数据结构管理的,所有的读写数据的请求最终都会改变这颗树的内容,在发出读请求时可能会传入 `Watcher` 注册一个回调函数,而写请求就可能会触发相应的回调,由 `WatchManager` 通知客户端数据的变化。 - -通知机制的实现其实还是比较简单的,通过读请求设置 `Watcher` 监听事件,写请求在触发事件时就能将通知发送给指定的客户端。 - -### 会话 - -**ZooKeeper 客户端通过 TCP 长连接连接到 ZooKeeper 服务集群**。**会话 (Session) 从第一次连接开始就已经建立,之后通过心跳检测机制来保持有效的会话状态**。通过这个连接,客户端可以发送请求并接收响应,同时也可以接收到 Watch 事件的通知。 - -每个 ZooKeeper 客户端配置中都配置了 ZooKeeper 服务器集群列表。启动时,客户端会遍历列表去尝试建立连接。如果失败,它会尝试连接下一个服务器,依次类推。 - -一旦一台客户端与一台服务器建立连接,这台服务器会为这个客户端创建一个新的会话。**每个会话都会有一个超时时间,若服务器在超时时间内没有收到任何请求,则相应会话被视为过期**。一旦会话过期,就无法再重新打开,且任何与该会话相关的临时 znode 都会被删除。 - -通常来说,会话应该长期存在,而这需要由客户端来保证。客户端可以通过心跳方式(ping)来保持会话不过期。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602182239.png) - -ZooKeeper 的会话具有四个属性: - -- `sessionID` - 会话 ID,唯一标识一个会话,每次客户端创建新的会话时,Zookeeper 都会为其分配一个全局唯一的 sessionID。 -- `TimeOut` - 会话超时时间,客户端在构造 Zookeeper 实例时,会配置 sessionTimeout 参数用于指定会话的超时时间,Zookeeper 客户端向服务端发送这个超时时间后,服务端会根据自己的超时时间限制最终确定会话的超时时间。 -- `TickTime` - 下次会话超时时间点,为了便于 Zookeeper 对会话实行”分桶策略”管理,同时为了高效低耗地实现会话的超时检查与清理,Zookeeper 会为每个会话标记一个下次会话超时时间点,其值大致等于当前时间加上 TimeOut。 -- `isClosing` - 标记一个会话是否已经被关闭,当服务端检测到会话已经超时失效时,会将该会话的 isClosing 标记为”已关闭”,这样就能确保不再处理来自该会话的心情求了。 - -Zookeeper 的会话管理主要是通过 `SessionTracker` 来负责,其采用了**分桶策略**(将类似的会话放在同一区块中进行管理)进行管理,以便 Zookeeper 对会话进行不同区块的隔离处理以及同一区块的统一处理。 - -## ZAB 协议 - -> ZooKeeper 并没有直接采用 Paxos 算法,而是采用了名为 ZAB 的一致性协议。**_ZAB 协议不是 Paxos 算法_**,只是比较类似,二者在操作上并不相同。 -> -> ZAB 协议是 Zookeeper 专门设计的一种**支持崩溃恢复的原子广播协议**。 -> -> ZAB 协议是 ZooKeeper 的数据一致性和高可用解决方案。 - -ZAB 协议定义了两个可以**无限循环**的流程: - -- **`选举 Leader`** - 用于故障恢复,从而保证高可用。 -- **`原子广播`** - 用于主从同步,从而保证数据一致性。 - -### 选举 Leader - -> **ZooKeeper 的故障恢复** -> -> ZooKeeper 集群采用一主(称为 Leader)多从(称为 Follower)模式,主从节点通过副本机制保证数据一致。 -> -> - **如果 Follower 节点挂了** - ZooKeeper 集群中的每个节点都会单独在内存中维护自身的状态,并且各节点之间都保持着通讯,**只要集群中有半数机器能够正常工作,那么整个集群就可以正常提供服务**。 -> - **如果 Leader 节点挂了** - 如果 Leader 节点挂了,系统就不能正常工作了。此时,需要通过 ZAB 协议的选举 Leader 机制来进行故障恢复。 -> -> ZAB 协议的选举 Leader 机制简单来说,就是:基于过半选举机制产生新的 Leader,之后其他机器将从新的 Leader 上同步状态,当有过半机器完成状态同步后,就退出选举 Leader 模式,进入原子广播模式。 - -#### 术语 - -- **myid** - 每个 Zookeeper 服务器,都需要在数据文件夹下创建一个名为 myid 的文件,该文件包含整个 Zookeeper 集群唯一的 ID(整数)。 -- **zxid** - 类似于 RDBMS 中的事务 ID,用于标识一次更新操作的 Proposal ID。为了保证顺序性,该 zkid 必须单调递增。因此 Zookeeper 使用一个 64 位的数来表示,高 32 位是 Leader 的 epoch,从 1 开始,每次选出新的 Leader,epoch 加一。低 32 位为该 epoch 内的序号,每次 epoch 变化,都将低 32 位的序号重置。这样保证了 zkid 的全局递增性。 - -#### 服务器状态 - -- **_LOOKING_** - 不确定 Leader 状态。该状态下的服务器认为当前集群中没有 Leader,会发起 Leader 选举 -- **_FOLLOWING_** - 跟随者状态。表明当前服务器角色是 Follower,并且它知道 Leader 是谁 -- **_LEADING_** - 领导者状态。表明当前服务器角色是 Leader,它会维护与 Follower 间的心跳 -- **_OBSERVING_** - 观察者状态。表明当前服务器角色是 Observer,与 Folower 唯一的不同在于不参与选举,也不参与集群写操作时的投票 - -#### 选票数据结构 - -每个服务器在进行领导选举时,会发送如下关键信息 - -- **_logicClock_** - 每个服务器会维护一个自增的整数,名为 logicClock,它表示这是该服务器发起的第多少轮投票 -- **_state_** - 当前服务器的状态 -- **_self_id_** - 当前服务器的 myid -- **_self_zxid_** - 当前服务器上所保存的数据的最大 zxid -- **_vote_id_** - 被推举的服务器的 myid -- **_vote_zxid_** - 被推举的服务器上所保存的数据的最大 zxid - -#### 投票流程 - -(1)**自增选举轮次** - Zookeeper 规定所有有效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对自己维护的 logicClock 进行自增操作。 - -(2)**初始化选票** - 每个服务器在广播自己的选票前,会将自己的投票箱清空。该投票箱记录了所收到的选票。例:服务器 2 投票给服务器 3,服务器 3 投票给服务器 1,则服务器 1 的投票箱为(2, 3), (3, 1), (1, 1)。票箱中只会记录每一投票者的最后一票,如投票者更新自己的选票,则其它服务器收到该新选票后会在自己票箱中更新该服务器的选票。 - -(3)**发送初始化选票** - 每个服务器最开始都是通过广播把票投给自己。 - -(4)**接收外部投票** - 服务器会尝试从其它服务器获取投票,并记入自己的投票箱内。如果无法获取任何外部投票,则会确认自己是否与集群中其它服务器保持着有效连接。如果是,则再次发送自己的投票;如果否,则马上与之建立连接。 - -(5)**判断选举轮次** - 收到外部投票后,首先会根据投票信息中所包含的 logicClock 来进行不同处理 - -- 外部投票的 logicClock 大于自己的 logicClock。说明该服务器的选举轮次落后于其它服务器的选举轮次,立即清空自己的投票箱并将自己的 logicClock 更新为收到的 logicClock,然后再对比自己之前的投票与收到的投票以确定是否需要变更自己的投票,最终再次将自己的投票广播出去。 -- 外部投票的 logicClock 小于自己的 logicClock。当前服务器直接忽略该投票,继续处理下一个投票。 -- 外部投票的 logickClock 与自己的相等。当时进行选票 PK。 - -(6)**选票 PK** - 选票 PK 是基于`(self_id, self_zxid)` 与 `(vote_id, vote_zxid)` 的对比 - -- 外部投票的 logicClock 大于自己的 logicClock,则将自己的 logicClock 及自己的选票的 logicClock 变更为收到的 logicClock -- 若 logicClock 一致,则对比二者的 vote_zxid,若外部投票的 vote_zxid 比较大,则将自己的票中的 vote_zxid 与 vote_myid 更新为收到的票中的 vote_zxid 与 vote_myid 并广播出去,另外将收到的票及自己更新后的票放入自己的票箱。如果票箱内已存在(self_myid, self_zxid)相同的选票,则直接覆盖 -- 若二者 vote_zxid 一致,则比较二者的 vote_myid,若外部投票的 vote_myid 比较大,则将自己的票中的 vote_myid 更新为收到的票中的 vote_myid 并广播出去,另外将收到的票及自己更新后的票放入自己的票箱 - -(7)**统计选票** - 如果已经确定有过半服务器认可了自己的投票(可能是更新后的投票),则终止投票。否则继续接收其它服务器的投票。 - -(8)**更新服务器状态** - 投票终止后,服务器开始更新自身状态。若过半的票投给了自己,则将自己的服务器状态更新为 LEADING,否则将自己的状态更新为 FOLLOWING - -通过以上流程分析,我们不难看出:要使 Leader 获得多数 Server 的支持,则 **ZooKeeper 集群节点数必须是奇数。且存活的节点数目不得少于 `N + 1`** 。 - -每个 Server 启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的 server 还会从磁盘快照中恢复数据和会话信息,zk 会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。 - -### 原子广播(Atomic Broadcast) - -**ZooKeeper 通过副本机制来实现高可用**。 - -那么,ZooKeeper 是如何实现副本机制的呢?答案是:ZAB 协议的原子广播。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) - -ZAB 协议的原子广播要求: - -**_所有的写请求都会被转发给 Leader,Leader 会以原子广播的方式通知 Follow。当半数以上的 Follow 已经更新状态持久化后,Leader 才会提交这个更新,然后客户端才会收到一个更新成功的响应_**。这有些类似数据库中的两阶段提交协议。 - -在整个消息的广播过程中,Leader 服务器会每个事务请求生成对应的 Proposal,并为其分配一个全局唯一的递增的事务 ID(ZXID),之后再对其进行广播。 - -## ZooKeeper 应用 - -> **ZooKeeper 可以用于发布/订阅、负载均衡、命令服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能** 。 - -### 命名服务 - -在分布式系统中,通常需要一个全局唯一的名字,如生成全局唯一的订单号等,ZooKeeper 可以通过顺序节点的特性来生成全局唯一 ID,从而可以对分布式系统提供命名服务。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602182548.png) - -### 配置管理 - -利用 ZooKeeper 的观察机制,可以将其作为一个高可用的配置存储器,允许分布式应用的参与者检索和更新配置文件。 - -### 分布式锁 - -可以通过 ZooKeeper 的临时节点和 Watcher 机制来实现分布式排它锁。 - -举例来说,有一个分布式系统,有三个节点 A、B、C,试图通过 ZooKeeper 获取分布式锁。 - -(1)访问 `/lock` (这个目录路径由程序自己决定),创建 **带序列号的临时节点(EPHEMERAL)** 。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602191358.png) - -(2)每个节点尝试获取锁时,拿到 `/locks`节点下的所有子节点(`id_0000`,`id_0001`,`id_0002`),**判断自己创建的节点是不是序列号最小的** - -- 如果序列号是最小的,则成功获取到锁。 - - 释放锁:执行完操作后,把创建的节点给删掉。 -- 如果不是,则监听比自己要小 1 的节点变化。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602192619.png) - -(3)释放锁,即删除自己创建的节点。 - -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200602192341.png) - -图中,NodeA 删除自己创建的节点 `id_0000`,NodeB 监听到变化,发现自己的节点已经是最小节点,即可获取到锁。 - -### 集群管理 - -ZooKeeper 还能解决大多数分布式系统中的问题: - -- 如可以通过创建临时节点来建立心跳检测机制。如果分布式系统的某个服务节点宕机了,则其持有的会话会超时,此时该临时节点会被删除,相应的监听事件就会被触发。 -- 分布式系统的每个服务节点还可以将自己的节点状态写入临时节点,从而完成状态报告或节点工作进度汇报。 -- 通过数据的订阅和发布功能,ZooKeeper 还能对分布式系统进行模块的解耦和任务的调度。 -- 通过监听机制,还能对分布式系统的服务节点进行动态上下线,从而实现服务的动态扩容。 - -### 选举 Leader 节点 - -分布式系统一个重要的模式就是主从模式 (Master/Salves),ZooKeeper 可以用于该模式下的 Matser 选举。可以让所有服务节点去竞争性地创建同一个 ZNode,由于 ZooKeeper 不能有路径相同的 ZNode,必然只有一个服务节点能够创建成功,这样该服务节点就可以成为 Master 节点。 - -### 队列管理 - -ZooKeeper 可以处理两种类型的队列: - -1. 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。 -2. 队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。 - -同步队列用 ZooKeeper 实现的实现思路如下: - -创建一个父目录 `/synchronizing`,每个成员都监控标志(Set Watch)位目录 `/synchronizing/start` 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 `/synchronizing/member_i` 的临时目录节点,然后每个成员获取 `/synchronizing` 目录的所有目录节点,也就是 `member_i`。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 `/synchronizing/start` 的出现,如果已经相等就创建 `/synchronizing/start`。 - -## ZooKeeper 的缺点 - -ZooKeeper 的监听是一次性的。 - -### ZooKeeper 不是为高可用性设计的 - -生产环境中常常需要通过多机房部署来容灾。出于成本考虑,一般多机房都是同时提供服务的,即一个机房撑不住所有流量。**ZooKeeper 集群只能有一个 Leader**,一旦机房之间连接出现故障,那么只有 Leader 所在的机房可以正常工作,其他机房只能停摆。于是所有流量集中到 Leader 所在的机房,由于处理不过来而导致崩溃。 - -即使是在同一个机房里面,由于网段的不同,在调整机房交换机的时候偶尔也会发生网段隔离的情况。实际上机房每个月基本上都会发生短暂的网络隔离之类的子网段调整。在那个时刻 ZooKeeper 将处于不可用状态。如果业务系统重度依赖 ZooKeeper(比如用 Dubbo 作为 RPC,且使用 ZooKeeper 作为注册中心),则系统的可用性将非常脆弱。 - -由于 ZooKeeper 对于网络隔离的极度敏感,导致 ZooKeeper 对于网络的任何风吹草动都会做出激烈反应。这使得 ZooKeeper 的**不可用**时间比较多。我们不能让 ZooKeeper 的**不可用**,变成系统的**不可用**。 - -### ZooKeeper 的选举过程速度很慢 - -互联网环境中,网络不稳定几乎是必然的,而 ZooKeeper 网络隔离非常敏感。一旦出现网络隔离,zookeeper 就要发起选举流程。 - -ZooKeeper 的选举流程通常耗时 30 到 120 秒,期间 ZooKeeper 由于没有 Leader,都是不可用的。 - -对于网络里面偶尔出现的,比如半秒一秒的网络隔离,ZooKeeper 会由于选举过程,而把不可用时间放大几十倍。 - -### ZooKeeper 的性能是有限的 - -典型的 ZooKeeper 的 TPS 大概是一万多,无法支撑每天动辄几十亿次的调用。因此,每次请求都去 ZooKeeper 获取业务系统信息是不可能的。 - -为此,ZooKeeper 的 client 必须自己缓存业务系统的信息。这就导致 ZooKeeper 提供的**强一致性**实际上是做不到的。如果我们需要强一致性,还需要其他机制来进行保障:比如用自动化脚本把业务系统的 old master 给 kill 掉,但是这可能会引发很多其他问题。 - -### ZooKeeper 无法进行有效的权限控制 - -ZooKeeper 的权限控制非常弱。在大型的复杂系统里面,使用 ZooKeeper 必须自己再额外的开发一套权限控制系统,通过那套权限控制系统再访问 ZooKeeper。 - -额外的权限控制系统不但增加了系统复杂性和维护成本,而且降低了系统的总体性能。 - -### 即使有了 ZooKeeper 也很难避免业务系统的数据不一致 - -由于 ZooKeeper 的性能限制,我们无法让每次系统内部调用都走 ZooKeeper,因此总有某些时刻,业务系统会存在两份数据(业务系统 client 那边缓存的业务系统信息是定时从 ZooKeeper 更新的,因此会有更新不同步的问题)。 - -如果要保持数据的强一致性,唯一的方法是“先 kill 掉当前 Leader,再在 ZooKeeper 上更新 Leader 信息”。是否要 kill 掉当前 Leader 这个问题上,程序是无法完全自动决定的(因为网络隔离的时候 ZooKeeper 已经不可用了,自动脚本没有全局信息,不管怎么做都可能是错的,什么都不做也可能是错的。当网络故障的时候,只有运维人员才有全局信息,程序是无法得知其他机房的情况的)。因此系统无法自动的保障数据一致性,必须要人工介入。而人工介入的典型时间是半个小时以上,我们不能让系统这么长时间不可用。因此我们必须在某个方向上进行妥协,最常见的妥协方式是放弃**强一致性**,而接受**最终一致性**。 - -如果我们需要人工介入才能保证*可靠的强一致性*,那么 ZooKeeper 的价值就大打折扣。 - -## 参考资料 - -- **官方** - - [ZooKeeper 官网](http://zookeeper.apache.org/) - - [ZooKeeper 官方文档](https://cwiki.apache.org/confluence/display/ZOOKEEPER) - - [ZooKeeper Github](https://github.com/apache/zookeeper) -- **书籍** - - [《Hadoop 权威指南(第四版)》](https://item.jd.com/12109713.html) - - [《从 Paxos 到 Zookeeper 分布式一致性原理与实践》](https://item.jd.com/11622772.html) -- **文章** - - [分布式服务框架 ZooKeeper -- 管理分布式环境中的数据](https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.html) - - [ZooKeeper 的功能以及工作原理](https://www.cnblogs.com/felixzh/p/5869212.html) - - [ZooKeeper 简介及核心概念](https://github.com/heibaiying/BigData-Notes/blob/master/notes/ZooKeeper%E7%AE%80%E4%BB%8B%E5%8F%8A%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5.md) - - [详解分布式协调服务 ZooKeeper](https://draveness.me/zookeeper-chubby) - - [深入浅出 Zookeeper(一) Zookeeper 架构及 FastLeaderElection 机制](http://www.jasongj.com/zookeeper/fastleaderelection/) - - [Introduction to Apache ZooKeeper](https://www.slideshare.net/sauravhaloi/introduction-to-apache-zookeeper) - - [Zookeeper 的优缺点](https://blog.csdn.net/wwwsq/article/details/7644445) \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/02.ZooKeeper\345\221\275\344\273\244.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/02.ZooKeeper\345\221\275\344\273\244.md" deleted file mode 100644 index 603e9e2693..0000000000 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/02.ZooKeeper\345\221\275\344\273\244.md" +++ /dev/null @@ -1,377 +0,0 @@ ---- -title: ZooKeeper命令 -date: 2022-02-19 13:27:21 -categories: - - 大数据 - - zookeeper -tags: - - 分布式 - - 大数据 - - ZooKeeper -permalink: /pages/13c5e2/ ---- - -# ZooKeeper 命令 - -> ZooKeeper 命令用于在 ZooKeeper 服务上执行操作。 - -## 启动服务和启动命令行 - -```bash -# 启动服务 -bin/zkServer.sh start - -# 启动命令行,不指定服务地址则默认连接到localhost:2181 -bin/zkCli.sh -server hadoop001:2181 -``` - -## 查看节点列表 - -### `ls` 命令 - -`ls` 命令用于查看某个路径下目录列表。 - -【语法】 - -```bash -ls path -``` - -> 说明: -> -> - **path**:代表路径。 - -【示例】 - -``` -[zk: localhost:2181(CONNECTED) 0] ls / -[cluster, controller_epoch, brokers, storm, zookeeper, admin, ...] -``` - -### `ls2` 命令 - -`ls2` 命令用于查看某个路径下目录列表,它比 ls 命令列出更多的详细信息。 - -【语法】 - -```bash -ls2 path -``` - -> 说明: -> -> - **path**:代表路径。 - -【示例】 - -```bash -[zk: localhost:2181(CONNECTED) 1] ls2 / -[cluster, controller_epoch, brokers, storm, zookeeper, admin, ....] -cZxid = 0x0 -ctime = Thu Jan 01 08:00:00 CST 1970 -mZxid = 0x0 -mtime = Thu Jan 01 08:00:00 CST 1970 -pZxid = 0x130 -cversion = 19 -dataVersion = 0 -aclVersion = 0 -ephemeralOwner = 0x0 -dataLength = 0 -numChildren = 11 -``` - -## 节点的增删改查 - -### `get` 命令 - -`get` 命令用于获取节点数据和状态信息。 - -【语法】 - -``` -get path [watch] -``` - -> 说明: -> -> - **path**:代表路径。 -> - **[watch]**:对节点进行事件监听。 - -【示例】 - -```bash -[zk: localhost:2181(CONNECTED) 31] get /hadoop -123456 #节点数据 -cZxid = 0x14b -ctime = Fri May 24 17:03:06 CST 2019 -mZxid = 0x14b -mtime = Fri May 24 17:03:06 CST 2019 -pZxid = 0x14b -cversion = 0 -dataVersion = 0 -aclVersion = 0 -ephemeralOwner = 0x0 -dataLength = 6 -numChildren = 0 -``` - -> 说明: -> -> 节点各个属性如下表。其中一个重要的概念是 Zxid(ZooKeeper Transaction Id),ZooKeeper 节点的每一次更改都具有唯一的 Zxid,如果 Zxid1 小于 Zxid2,则 Zxid1 的更改发生在 Zxid2 更改之前。 -> -> | **状态属性** | **说明** | -> | -------------- | ------------------------------------------------------------------------------------------ | -> | cZxid | 数据节点创建时的事务 ID | -> | ctime | 数据节点创建时的时间 | -> | mZxid | 数据节点最后一次更新时的事务 ID | -> | mtime | 数据节点最后一次更新时的时间 | -> | pZxid | 数据节点的子节点最后一次被修改时的事务 ID | -> | cversion | 子节点的更改次数 | -> | dataVersion | 节点数据的更改次数 | -> | aclVersion | 节点的 ACL 的更改次数 | -> | ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 | -> | dataLength | 数据内容的长度 | -> | numChildren | 数据节点当前的子节点个数 | - -### `stat` 命令 - -`stat` 命令用于查看节点状态信息。它的返回值和 `get` 命令类似,但不会返回节点数据。 - -【语法】 - -``` -stat path [watch] -``` - -- **path**:代表路径。 -- **[watch]**:对节点进行事件监听。 - -【示例】 - -```bash -[zk: localhost:2181(CONNECTED) 32] stat /hadoop -cZxid = 0x14b -ctime = Fri May 24 17:03:06 CST 2019 -mZxid = 0x14b -mtime = Fri May 24 17:03:06 CST 2019 -pZxid = 0x14b -cversion = 0 -dataVersion = 0 -aclVersion = 0 -ephemeralOwner = 0x0 -dataLength = 6 -numChildren = 0 -``` - -### `create` 命令 - -`create` 命令用于创建节点并赋值。 - -【语法】 - -```bash -create [-s] [-e] path data acl -``` - -> 说明: -> -> - **[-s][-e]**:-s 和 -e 都是可选的,-s 代表顺序节点,-e 代表临时节点,注意其中 -s 和 -e 可以同时使用的,并且临时节点不能再创建子节点。 -> - 默认情况下,所有 znode 都是持久的。 -> - 顺序节点保证 znode 路径将是唯一的。 -> - 临时节点会在会话过期或客户端断开连接时被自动删除。 -> - **path**:指定要创建节点的路径,比如 **/hadoop**。 -> - **data**:要在此节点存储的数据。 -> - **acl**:访问权限相关,默认是 world,相当于全世界都能访问。 - -【示例】创建持久节点 - -```bash -[zk: localhost:2181(CONNECTED) 4] create /hadoop 123456 -Created /hadoop -``` - -【示例】创建有序节点,此时创建的节点名为指定节点名 + 自增序号: - -```bash -[zk: localhost:2181(CONNECTED) 23] create -s /a "aaa" -Created /a0000000022 -[zk: localhost:2181(CONNECTED) 24] create -s /b "bbb" -Created /b0000000023 -[zk: localhost:2181(CONNECTED) 25] create -s /c "ccc" -Created /c0000000024 -``` - -【示例】创建临时节点: - -```bash -[zk: localhost:2181(CONNECTED) 26] create -e /tmp "tmp" -Created /tmp -``` - -### `set` 命令 - -`set` 命令用于修改节点存储的数据。 - -【语法】 - -``` -set path data [version] -``` - -> 说明: -> -> - **path**:节点路径。 -> - **data**:需要存储的数据。 -> - **[version]**:可选项,版本号(可用作乐观锁)。 - -【示例】 - -```bash -[zk: localhost:2181(CONNECTED) 33] set /hadoop 345 -cZxid = 0x14b -ctime = Fri May 24 17:03:06 CST 2019 -mZxid = 0x14c -mtime = Fri May 24 17:13:05 CST 2019 -pZxid = 0x14b -cversion = 0 -dataVersion = 1 # 注意更改后此时版本号为 1,默认创建时为 0 -aclVersion = 0 -ephemeralOwner = 0x0 -dataLength = 3 -numChildren = 0 -``` - -也可以基于版本号进行更改,此时类似于乐观锁机制,当你传入的数据版本号 (dataVersion) 和当前节点的数据版本号不符合时,zookeeper 会拒绝本次修改: - -```bash -[zk: localhost:2181(CONNECTED) 34] set /hadoop 678 0 -version No is not valid : /hadoop #无效的版本号 -``` - -### `delete` 命令 - -`delete` 命令用于删除某节点。 - -【语法】 - -``` -delete path [version] -``` - -> 说明: -> -> - **path**:节点路径。 -> - **[version]**:可选项,版本号(同 set 命令)。和更新节点数据一样,也可以传入版本号,当你传入的数据版本号 (dataVersion) 和当前节点的数据版本号不符合时,zookeeper 不会执行删除操作。 - -【示例】 - -```bash -[zk: localhost:2181(CONNECTED) 36] delete /hadoop 0 -version No is not valid : /hadoop #无效的版本号 -[zk: localhost:2181(CONNECTED) 37] delete /hadoop 1 -[zk: localhost:2181(CONNECTED) 38] -``` - -`delete` 命令不能删除带有子节点的节点。如果想要删除节点及其子节点,可以使用 `deleteall path` - -## 监听器 - -针对每个节点的操作,都会有一个监听者(watcher)。 - -- 当监听的某个对象(znode)发生了变化,则触发监听事件。 -- zookeeper 中的监听事件是一次性的,触发后立即销毁。 -- 父节点,子节点的增删改都能够触发其监听者(watcher) -- 针对不同类型的操作,触发的 watcher 事件也不同: - - 父节点 Watcher 事件 - - 创建父节点触发:NodeCreated - - 修改节点数据触发:NodeDatachanged - - 删除节点数据触发:NodeDeleted - - 子节点 Watcher 事件 - - 创建子节点触发:NodeChildrenChanged - - 删除子节点触发:NodeChildrenChanged - - 修改子节点不触发事件 - -### get path - -使用 `get path -w` 注册的监听器能够在节点内容发生改变的时候,向客户端发出通知。需要注意的是 zookeeper 的触发器是一次性的 (One-time trigger),即触发一次后就会立即失效。 - -```bash -[zk: localhost:2181(CONNECTED) 4] get /hadoop -w -[zk: localhost:2181(CONNECTED) 5] set /hadoop 45678 -WATCHER:: -WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点值改变 -``` - -> get path [watch] 在当前版本已废弃 - -### stat path - -使用 `stat path -w` 注册的监听器能够在节点状态发生改变的时候,向客户端发出通知。 - -```bash -[zk: localhost:2181(CONNECTED) 7] stat path -w -[zk: localhost:2181(CONNECTED) 8] set /hadoop 112233 -WATCHER:: -WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点值改变 -``` - -> stat path [watch] 在当前版本已废弃 - -### ls\ls2 path - -使用 `ls path -w` 或 `ls2 path -w` 注册的监听器能够监听该节点下所有**子节点**的增加和删除操作。 - -```bash -[zk: localhost:2181(CONNECTED) 9] ls /hadoop -w -[] -[zk: localhost:2181(CONNECTED) 10] create /hadoop/yarn "aaa" -WATCHER:: -WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hadoop -``` - -> ls path [watch] 和 ls2 path [watch] 在当前版本已废弃 - -## 辅助命令 - -使用 `help` 可以查看所有命令帮助信息。 - -使用 `history` 可以查看最近 10 条历史记录。 - -## zookeeper 四字命令 - -| 命令 | 功能描述 | -| ---- | --------------------------------------------------------------------------------------------------------------------------- | -| conf | 打印服务配置的详细信息。 | -| cons | 列出连接到此服务器的所有客户端的完整连接/会话详细信息。包括接收/发送的数据包数量,会话 ID,操作延迟,上次执行的操作等信息。 | -| dump | 列出未完成的会话和临时节点。这只适用于 Leader 节点。 | -| envi | 打印服务环境的详细信息。 | -| ruok | 测试服务是否处于正确状态。如果正确则返回“imok”,否则不做任何相应。 | -| stat | 列出服务器和连接客户端的简要详细信息。 | -| wchs | 列出所有 watch 的简单信息。 | -| wchc | 按会话列出服务器 watch 的详细信息。 | -| wchp | 按路径列出服务器 watch 的详细信息。 | - -> 更多四字命令可以参阅官方文档:[https://zookeeper.apache.org/doc/current/zookeeperAdmin.html](https://zookeeper.apache.org/doc/current/zookeeperAdmin.html) - -使用前需要使用 `yum install nc` 安装 nc 命令,使用示例如下: - -```bash -[root@hadoop001 bin]# echo stat | nc localhost 2181 -Zookeeper version: 3.4.13-2d71af4dbe22557fda74f9a9b4309b15a7487f03, -built on 06/29/2018 04:05 GMT -Clients: - /0:0:0:0:0:0:0:1:50584[1](queued=0,recved=371,sent=371) - /0:0:0:0:0:0:0:1:50656[0](queued=0,recved=1,sent=0) -Latency min/avg/max: 0/0/19 -Received: 372 -Sent: 371 -Connections: 2 -Outstanding: 0 -Zxid: 0x150 -Mode: standalone -Node count: 167 -``` - -## 参考资料 - -- [Zookeeper 客户端基础命令使用](https://www.runoob.com/w3cnote/zookeeper-bs-command.html) \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/03.ZooKeeper\350\277\220\347\273\264.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/03.ZooKeeper\350\277\220\347\273\264.md" deleted file mode 100644 index ee320fc0d9..0000000000 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/03.ZooKeeper\350\277\220\347\273\264.md" +++ /dev/null @@ -1,188 +0,0 @@ ---- -title: ZooKeeper运维 -date: 2020-06-02 22:28:38 -categories: - - 大数据 - - zookeeper -tags: - - 分布式 - - 大数据 - - ZooKeeper -permalink: /pages/bb5e61/ ---- - -# ZooKeeper 运维指南 - -## 单点服务部署 - -在安装 ZooKeeper 之前,请确保你的系统是在以下任一操作系统上运行: - -- **任意 Linux OS** - 支持开发和部署。适合演示应用程序。 -- **Windows OS** - 仅支持开发。 -- **Mac OS** - 仅支持开发。 - -安装步骤如下: - -### 下载解压 - -进入官方下载地址:[http://zookeeper.apache.org/releases.html#download](http://zookeeper.apache.org/releases.html#download) ,选择合适版本。 - -解压到本地: - -```bash -tar -zxf zookeeper-3.4.6.tar.gz -cd zookeeper-3.4.6 -``` - -### 环境变量 - -执行 `vim /etc/profile`,添加环境变量: - -```bash -export ZOOKEEPER_HOME=/usr/app/zookeeper-3.4.14 -export PATH=$ZOOKEEPER_HOME/bin:$PATH -``` - -再执行 `source /etc/profile` , 使得配置的环境变量生效。 - -### 修改配置 - -你必须创建 `conf/zoo.cfg` 文件,否则启动时会提示你没有此文件。 - -初次尝试,不妨直接使用 Kafka 提供的模板配置文件 `conf/zoo_sample.cfg`: - -```bash -cp conf/zoo_sample.cfg conf/zoo.cfg -``` - -修改后完整配置如下: - -```properties -# The number of milliseconds of each tick -tickTime=2000 -# The number of ticks that the initial -# synchronization phase can take -initLimit=10 -# The number of ticks that can pass between -# sending a request and getting an acknowledgement -syncLimit=5 -# the directory where the snapshot is stored. -# do not use /tmp for storage, /tmp here is just -# example sakes. -dataDir=/usr/local/zookeeper/data -dataLogDir=/usr/local/zookeeper/log -# the port at which the clients will connect -clientPort=2181 -# the maximum number of client connections. -# increase this if you need to handle more clients -#maxClientCnxns=60 -# -# Be sure to read the maintenance section of the -# administrator guide before turning on autopurge. -# -# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance -# -# The number of snapshots to retain in dataDir -#autopurge.snapRetainCount=3 -# Purge task interval in hours -# Set to "0" to disable auto purge feature -#autopurge.purgeInterval=1 -``` - -配置参数说明: - -- **tickTime**:用于计算的基础时间单元。比如 session 超时:N\*tickTime; -- **initLimit**:用于集群,允许从节点连接并同步到 master 节点的初始化连接时间,以 tickTime 的倍数来表示; -- **syncLimit**:用于集群, master 主节点与从节点之间发送消息,请求和应答时间长度(心跳机制); -- **dataDir**:数据存储位置; -- **dataLogDir**:日志目录; -- **clientPort**:用于客户端连接的端口,默认 2181 - -### 启动服务 - -执行以下命令 - -```bash -bin/zkServer.sh start -``` - -执行此命令后,你将收到以下响应 - -```bash -JMX enabled by default -Using config: /Users/../zookeeper-3.4.6/bin/../conf/zoo.cfg -Starting zookeeper ... STARTED -``` - -### 停止服务 - -可以使用以下命令停止 zookeeper 服务器。 - -```bash -bin/zkServer.sh stop -``` - -## 集群服务部署 - -分布式系统节点数一般都要求是奇数,且最少为 3 个节点,Zookeeper 也不例外。 - -这里,规划一个含 3 个节点的最小 ZooKeeper 集群,主机名分别为 hadoop001,hadoop002,hadoop003 。 - -### 修改配置 - -修改配置文件 `zoo.cfg`,内容如下: - -```properties -tickTime=2000 -initLimit=10 -syncLimit=5 -dataDir=/usr/local/zookeeper-cluster/data/ -dataLogDir=/usr/local/zookeeper-cluster/log/ -clientPort=2181 - -# server.1 这个1是服务器的标识,可以是任意有效数字,标识这是第几个服务器节点,这个标识要写到dataDir目录下面myid文件里 -# 指名集群间通讯端口和选举端口 -server.1=hadoop001:2287:3387 -server.2=hadoop002:2287:3387 -server.3=hadoop003:2287:3387 -``` - -### 标识节点 - -分别在三台主机的 `dataDir` 目录下新建 `myid` 文件,并写入对应的节点标识。Zookeeper 集群通过 `myid` 文件识别集群节点,并通过上文配置的节点通信端口和选举端口来进行节点通信,选举出 Leader 节点。 - -创建存储目录: - -```bash -# 三台主机均执行该命令 -mkdir -vp /usr/local/zookeeper-cluster/data/ -``` - -创建并写入节点标识到 `myid` 文件: - -```bash -# hadoop001主机 -echo "1" > /usr/local/zookeeper-cluster/data/myid -# hadoop002主机 -echo "2" > /usr/local/zookeeper-cluster/data/myid -# hadoop003主机 -echo "3" > /usr/local/zookeeper-cluster/data/myid -``` - -### 启动集群 - -分别在三台主机上,执行如下命令启动服务: - -```bash -/usr/app/zookeeper-cluster/zookeeper/bin/zkServer.sh start -``` - -### 集群验证 - -启动后使用 `zkServer.sh status` 查看集群各个节点状态。 - -## 参考资料 - -- [Zookeeper 安装](https://www.w3cschool.cn/zookeeper/zookeeper_installation.html) -- [Zookeeper 单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper%E5%8D%95%E6%9C%BA%E7%8E%AF%E5%A2%83%E5%92%8C%E9%9B%86%E7%BE%A4%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA.md) -- [Zookeeper 客户端基础命令使用](https://www.runoob.com/w3cnote/zookeeper-bs-command.html) \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/04.ZooKeeperJavaApi.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/04.ZooKeeperJavaApi.md" deleted file mode 100644 index a6a3f54565..0000000000 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/04.ZooKeeperJavaApi.md" +++ /dev/null @@ -1,640 +0,0 @@ ---- -title: ZooKeeper Java API -date: 2022-02-19 13:27:21 -categories: - - 大数据 - - zookeeper -tags: - - 分布式 - - 大数据 - - ZooKeeper -permalink: /pages/3cb33a/ ---- - -# ZooKeeper Java API - -> ZooKeeper 是 Apache 的顶级项目。**ZooKeeper 为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面,ZooKeeper 并没有直接采用 Paxos 算法,而是采用了名为 ZAB 的一致性协议**。 -> -> ZooKeeper 主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来**维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理**。 -> -> 很多大名鼎鼎的框架都基于 ZooKeeper 来实现分布式高可用,如:Dubbo、Kafka 等。 -> -> ZooKeeper 官方支持 Java 和 C 的 Client API。ZooKeeper 社区为大多数语言(.NET,python 等)提供非官方 API。 - -## ZooKeeper 官方客户端 - -### ZooKeeper 客户端简介 - -客户端和服务端交互遵循以下基本步骤: - -1. 客户端连接 ZooKeeper 服务端集群任意工作节点,该节点为客户端分配会话 ID。 -2. 为了保持通信,客户端需要和服务端保持心跳(实质上就是 ping )。否则,ZooKeeper 服务会话超时时间内未收到客户端请求,会将会话视为过期。这种情况下,客户端如果要通信,就需要重新连接。 -3. 只要会话 ID 处于活动状态,就可以执行读写 znode 操作。 -4. 所有任务完成后,客户端断开与 ZooKeeper 服务端集群的连接。如果客户端长时间不活动,则 ZooKeeper 集合将自动断开客户端。 - -ZooKeeper 官方客户端的核心是 **`ZooKeeper` 类**。它在其构造函数中提供了连接 ZooKeeper 服务的配置选项,并提供了访问 ZooKeeper 数据的方法。 - -> 其主要操作如下: -> -> - **`connect`** - 连接 ZooKeeper 服务 -> - **`create`** - 创建 znode -> - **`exists`** - 检查 znode 是否存在及其信息 -> - **`getACL`** / **`setACL`**- 获取/设置一个 znode 的 ACL -> - **`getData`** / **`setData`**- 获取/设置一个 znode 的数据 -> - **`getChildren`** - 获取特定 znode 中的所有子节点 -> - **`delete`** - 删除特定的 znode 及其所有子项 -> - **`close`** - 关闭连接 - -ZooKeeper 官方客户端的使用方法是在 maven 项目的 pom.xml 中添加: - -```xml - - org.apache.zookeeper - zookeeper - 3.7.0 - -``` - -### 创建连接 - -ZooKeeper 类通过其构造函数提供连接 ZooKeeper 服务的功能。其构造函数的定义如下: - -```java -ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher) -``` - -> 参数说明: -> -> - **`connectionString`** - ZooKeeper 集群的主机列表。 -> - **`sessionTimeout`** - 会话超时时间(以毫秒为单位)。 -> - **watcher** - 实现监视机制的回调。当被监控的 znode 状态发生变化时,ZooKeeper 服务端的 `WatcherManager` 会主动调用传入的 Watcher ,推送状态变化给客户端。 - -【示例】连接 ZooKeeper - -```java -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZooKeeper; -import org.junit.jupiter.api.*; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; - -/** - * ZooKeeper 官方客户端测试例 - * - * @author Zhang Peng - * @since 2022-02-19 - */ -@DisplayName("ZooKeeper 官方客户端测试例") -public class ZooKeeperTest { - - /** - * ZooKeeper 连接实例 - */ - private static ZooKeeper zk; - - /** - * 创建 ZooKeeper 连接 - */ - @BeforeAll - public static void init() throws IOException, InterruptedException { - final String HOST = "localhost:2181"; - CountDownLatch latch = new CountDownLatch(1); - zk = new ZooKeeper(HOST, 5000, watcher -> { - if (watcher.getState() == Watcher.Event.KeeperState.SyncConnected) { - latch.countDown(); - } - }); - latch.await(); - } - - /** - * 关闭 ZooKeeper 连接 - */ - @AfterAll - public static void destroy() throws InterruptedException { - if (zk != null) { - zk.close(); - } - } - - /** - * 建立连接 - */ - @Test - public void getState() { - ZooKeeper.States state = zk.getState(); - Assertions.assertTrue(state.isAlive()); - } - -} -``` - -> 说明: -> -> 添加一个 `connect` 方法,用于创建一个 `ZooKeeper` 对象,用于连接到 ZooKeeper 服务。 -> -> 这里 `CountDownLatch` 用于停止(等待)主进程,直到客户端与 ZooKeeper 集合连接。 -> -> `ZooKeeper` 对象通过监听器回调来监听连接状态。一旦客户端与 ZooKeeper 建立连接,监听器回调就会被调用;并且监听器回调函数调用 `CountDownLatch` 的 `countDown` 方法来释放锁,在主进程中 `await`。 - -### 节点增删改查 - -#### 判断节点是否存在 - -ZooKeeper 类提供了 `exists` 方法来检查 znode 的存在。如果指定的 znode 存在,则返回一个 znode 的元数据。 - -`exists` 方法的签名如下: - -``` -exists(String path, boolean watcher) -``` - -- **path**- Znode 路径 -- **watcher** - 布尔值,用于指定是否监视指定的 znode - -【示例】 - -```java -Stat stat = zk.exists("/", true); -Assertions.assertNotNull(stat); -``` - -#### 创建节点 - -`ZooKeeper` 类的 `create` 方法用于在 ZooKeeper 中创建一个新节点(znode)。 - -`create` 方法的签名如下: - -``` -create(String path, byte[] data, List acl, CreateMode createMode) -``` - -- **path** - Znode 路径。例如,/myapp1,/myapp2,/myapp1/mydata1,myapp2/mydata1/myanothersubdata -- **data** - 要存储在指定 znode 路径中的数据 -- **acl** - 要创建的节点的访问控制列表。ZooKeeper API 提供了一个静态接口 **ZooDefs.Ids** 来获取一些基本的 acl 列表。例如,ZooDefs.Ids.OPEN_ACL_UNSAFE 返回打开 znode 的 acl 列表。 -- **createMode** - 节点的类型,即临时,顺序或两者。这是一个**枚举**。 - -【示例】 - -```java -private static final String path = "/mytest"; - -String text = "My first zookeeper app"; -zk.create(path, text.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); -Stat stat = zk.exists(path, true); -Assertions.assertNotNull(stat); -``` - -#### 删除节点 - -ZooKeeper 类提供了 `delete` 方法来删除指定的 znode。 - -`delete` 方法的签名如下: - -``` -delete(String path, int version) -``` - -- **path** - Znode 路径。 -- **version** - znode 的当前版本。 - -让我们创建一个新的 Java 应用程序来了解 ZooKeeper API 的 **delete** 功能。创建文件 **ZKDelete.java** 。在 main 方法中,使用 **ZooKeeperConnection** 对象创建一个 ZooKeeper 对象 **zk** 。然后,使用指定的路径和版本号调用 **zk** 对象的 **delete** 方法。 - -删除 znode 的完整程序代码如下: - -【示例】 - -```java -zk.delete(path, zk.exists(path, true).getVersion()); -Stat stat = zk.exists(path, true); -Assertions.assertNull(stat); -``` - -#### 获取节点数据 - -ZooKeeper 类提供 **getData** 方法来获取附加在指定 znode 中的数据及其状态。 **getData** 方法的签名如下: - -``` -getData(String path, Watcher watcher, Stat stat) -``` - -- **path** - Znode 路径。 -- **watcher** - 监听器类型的回调函数。当指定的 znode 的数据改变时,ZooKeeper 集合将通过监听器回调进行通知。这是一次性通知。 -- **stat** - 返回 znode 的元数据。 - -【示例】 - -```java -byte[] data = zk.getData(path, false, null); -String text1 = new String(data); -Assertions.assertEquals(text, text1); -System.out.println(text1); -``` - -#### 设置节点数据 - -ZooKeeper 类提供 **setData** 方法来修改指定 znode 中附加的数据。 **setData** 方法的签名如下: - -``` -setData(String path, byte[] data, int version) -``` - -- **path**- Znode 路径 -- **data** - 要存储在指定 znode 路径中的数据。 -- **version**- znode 的当前版本。每当数据更改时,ZooKeeper 会更新 znode 的版本号。 - -【示例】 - -```java -String text = "含子节点的节点"; -zk.create(path, text.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); -zk.create(path + "/1", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); -zk.create(path + "/2", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); -List actualList = zk.getChildren(path, false); -for (String child : actualList) { - System.out.println(child); -} -``` - -#### 获取子节点 - -ZooKeeper 类提供 **getChildren** 方法来获取特定 znode 的所有子节点。 **getChildren** 方法的签名如下: - -``` -getChildren(String path, Watcher watcher) -``` - -- **path** - Znode 路径。 -- **watcher** - 监听器类型的回调函数。当指定的 znode 被删除或 znode 下的子节点被创建/删除时,ZooKeeper 集合将进行通知。这是一次性通知。 - -【示例】 - -```java -@Test -public void getChildren() throws InterruptedException, KeeperException { - byte[] data = "My first zookeeper app".getBytes(); - zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - zk.create(path + "/1", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - zk.create(path + "/2", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - List actualList = zk.getChildren(path, false); - List expectedList = CollectionUtil.newArrayList("1", "2"); - Assertions.assertTrue(CollectionUtil.containsAll(expectedList, actualList)); - for (String child : actualList) { - System.out.println(child); - } -} -``` - -## Curator 客户端 - -### Curator 客户端简介 - -Curator 客户端的使用方法是在 maven 项目的 pom.xml 中添加: - -```xml - - org.apache.curator - curator-recipes - 5.1.0 - -``` - -### 创建连接 - -```java -import org.apache.curator.RetryPolicy; -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.CuratorFrameworkFactory; -import org.apache.curator.framework.imps.CuratorFrameworkState; -import org.apache.curator.retry.RetryNTimes; -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.ZooDefs; -import org.junit.jupiter.api.*; - -import java.nio.charset.StandardCharsets; - -public class CuratorTest { - - /** - * Curator ZooKeeper 连接实例 - */ - private static CuratorFramework client = null; - private static final String path = "/mytest"; - - /** - * 创建连接 - */ - @BeforeAll - public static void init() { - // 重试策略 - RetryPolicy retryPolicy = new RetryNTimes(3, 5000); - client = CuratorFrameworkFactory.builder() - .connectString("localhost:2181") - .sessionTimeoutMs(10000).retryPolicy(retryPolicy) - .namespace("workspace").build(); //指定命名空间后,client 的所有路径操作都会以 /workspace 开头 - client.start(); - } - - /** - * 关闭连接 - */ - @AfterAll - public static void destroy() { - if (client != null) { - client.close(); - } - } - -} -``` - -### 节点增删改查 - -#### 判断节点是否存在 - -```java -Stat stat = client.checkExists().forPath(path); -Assertions.assertNull(stat); -``` - -#### 判读服务状态 - -```java -CuratorFrameworkState state = client.getState(); -Assertions.assertEquals(CuratorFrameworkState.STARTED, state); -``` - -#### 创建节点 - -```java -// 创建节点 -String text = "Hello World"; -client.create().creatingParentsIfNeeded() - .withMode(CreateMode.PERSISTENT) //节点类型 - .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) - .forPath(path, text.getBytes(StandardCharsets.UTF_8)); -``` - -#### 删除节点 - -```java -client.delete() - .guaranteed() // 如果删除失败,会继续执行,直到成功 - .deletingChildrenIfNeeded() // 如果有子节点,则递归删除 - .withVersion(stat.getVersion()) // 传入版本号,如果版本号错误则拒绝删除操作,并抛出 BadVersion 异常 - .forPath(path); -``` - -#### 获取节点数据 - -```java -byte[] data = client.getData().forPath(path); -Assertions.assertEquals(text, new String(data)); -System.out.println("修改前的节点数据:" + new String(data)); -``` - -#### 设置节点数据 - -```java -String text2 = "try again"; -client.setData() - .withVersion(client.checkExists().forPath(path).getVersion()) - .forPath(path, text2.getBytes(StandardCharsets.UTF_8)); -``` - -#### 获取子节点 - -```java -List children = client.getChildren().forPath(path); -for (String s : children) { - System.out.println(s); -} -List expectedList = CollectionUtil.newArrayList("1", "2"); -Assertions.assertTrue(CollectionUtil.containsAll(expectedList, children)); -``` - -### 监听事件 - -#### 创建一次性监听 - -和 Zookeeper 原生监听一样,使用 `usingWatcher` 注册的监听是一次性的,即监听只会触发一次,触发后就销毁。 - -【示例】 - -```java -// 设置监听器 -client.getData().usingWatcher(new CuratorWatcher() { - public void process(WatchedEvent event) { - System.out.println("节点 " + event.getPath() + " 发生了事件:" + event.getType()); - } -}).forPath(path); - -// 第一次修改 -client.setData() - .withVersion(client.checkExists().forPath(path).getVersion()) - .forPath(path, "第一次修改".getBytes(StandardCharsets.UTF_8)); - -// 第二次修改 -client.setData() - .withVersion(client.checkExists().forPath(path).getVersion()) - .forPath(path, "第二次修改".getBytes(StandardCharsets.UTF_8)); -``` - -输出 - -``` -节点 /mytest 发生了事件:NodeDataChanged -``` - -说明 - -修改两次数据,但是监听器只会监听第一次修改。 - -#### 创建永久监听 - -Curator 还提供了创建永久监听的 API,其使用方式如下: - -```java -// 设置监听器 -CuratorCache curatorCache = CuratorCache.builder(client, path).build(); -PathChildrenCacheListener pathChildrenCacheListener = new PathChildrenCacheListener() { - @Override - public void childEvent(CuratorFramework framework, PathChildrenCacheEvent event) throws Exception { - System.out.println("节点 " + event.getData().getPath() + " 发生了事件:" + event.getType()); - } -}; -CuratorCacheListener listener = CuratorCacheListener.builder() - .forPathChildrenCache(path, client, - pathChildrenCacheListener) - .build(); -curatorCache.listenable().addListener(listener); -curatorCache.start(); - -// 第一次修改 -client.setData() - .withVersion(client.checkExists().forPath(path).getVersion()) - .forPath(path, "第一次修改".getBytes(StandardCharsets.UTF_8)); - -// 第二次修改 -client.setData() - .withVersion(client.checkExists().forPath(path).getVersion()) - .forPath(path, "第二次修改".getBytes(StandardCharsets.UTF_8)); -``` - -#### 监听子节点 - -这里以监听 `/hadoop` 下所有子节点为例,实现方式如下: - -```java -// 创建节点 -String text = "Hello World"; -client.create().creatingParentsIfNeeded() - .withMode(CreateMode.PERSISTENT) //节点类型 - .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) - .forPath(path, text.getBytes(StandardCharsets.UTF_8)); -client.create().creatingParentsIfNeeded() - .withMode(CreateMode.PERSISTENT) //节点类型 - .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) - .forPath(path + "/1", text.getBytes(StandardCharsets.UTF_8)); -client.create().creatingParentsIfNeeded() - .withMode(CreateMode.PERSISTENT) //节点类型 - .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) - .forPath(path + "/2", text.getBytes(StandardCharsets.UTF_8)); - -// 设置监听器 -// 第三个参数代表除了节点状态外,是否还缓存节点内容 -PathChildrenCache childrenCache = new PathChildrenCache(client, path, true); -/* - * StartMode 代表初始化方式: - * NORMAL: 异步初始化 - * BUILD_INITIAL_CACHE: 同步初始化 - * POST_INITIALIZED_EVENT: 异步并通知,初始化之后会触发 INITIALIZED 事件 - */ -childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT); - -List childDataList = childrenCache.getCurrentData(); -System.out.println("当前数据节点的子节点列表:"); -childDataList.forEach(x -> System.out.println(x.getPath())); - -childrenCache.getListenable().addListener(new PathChildrenCacheListener() { - public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) { - switch (event.getType()) { - case INITIALIZED: - System.out.println("childrenCache 初始化完成"); - break; - case CHILD_ADDED: - // 需要注意的是: 即使是之前已经存在的子节点,也会触发该监听,因为会把该子节点加入 childrenCache 缓存中 - System.out.println("增加子节点:" + event.getData().getPath()); - break; - case CHILD_REMOVED: - System.out.println("删除子节点:" + event.getData().getPath()); - break; - case CHILD_UPDATED: - System.out.println("被修改的子节点的路径:" + event.getData().getPath()); - System.out.println("修改后的数据:" + new String(event.getData().getData())); - break; - } - } -}); - -// 第一次修改 -client.setData() - .forPath(path + "/1", "第一次修改".getBytes(StandardCharsets.UTF_8)); - -// 第二次修改 -client.setData() - .forPath(path + "/1", "第二次修改".getBytes(StandardCharsets.UTF_8)); -``` - -### ACL 权限管理 - -```java -public class AclOperation { - - private CuratorFramework client = null; - private static final String zkServerPath = "192.168.0.226:2181"; - private static final String nodePath = "/mytest/hdfs"; - - @Before - public void prepare() { - RetryPolicy retryPolicy = new RetryNTimes(3, 5000); - client = CuratorFrameworkFactory.builder() - .authorization("digest", "heibai:123456".getBytes()) //等价于 addauth 命令 - .connectString(zkServerPath) - .sessionTimeoutMs(10000).retryPolicy(retryPolicy) - .namespace("workspace").build(); - client.start(); - } - - /** - * 新建节点并赋予权限 - */ - @Test - public void createNodesWithAcl() throws Exception { - List aclList = new ArrayList<>(); - // 对密码进行加密 - String digest1 = DigestAuthenticationProvider.generateDigest("heibai:123456"); - String digest2 = DigestAuthenticationProvider.generateDigest("ying:123456"); - Id user01 = new Id("digest", digest1); - Id user02 = new Id("digest", digest2); - // 指定所有权限 - aclList.add(new ACL(Perms.ALL, user01)); - // 如果想要指定权限的组合,中间需要使用 | ,这里的|代表的是位运算中的 按位或 - aclList.add(new ACL(Perms.DELETE | Perms.CREATE, user02)); - - // 创建节点 - byte[] data = "abc".getBytes(); - client.create().creatingParentsIfNeeded() - .withMode(CreateMode.PERSISTENT) - .withACL(aclList, true) - .forPath(nodePath, data); - } - - - /** - * 给已有节点设置权限,注意这会删除所有原来节点上已有的权限设置 - */ - @Test - public void SetAcl() throws Exception { - String digest = DigestAuthenticationProvider.generateDigest("admin:admin"); - Id user = new Id("digest", digest); - client.setACL() - .withACL(Collections.singletonList(new ACL(Perms.READ | Perms.DELETE, user))) - .forPath(nodePath); - } - - /** - * 获取权限 - */ - @Test - public void getAcl() throws Exception { - List aclList = client.getACL().forPath(nodePath); - ACL acl = aclList.get(0); - System.out.println(acl.getId().getId() - + "是否有删读权限:" + (acl.getPerms() == (Perms.READ | Perms.DELETE))); - } - - @After - public void destroy() { - if (client != null) { - client.close(); - } - } -} -``` - -## 参考资料 - -- **官方** - - [ZooKeeper 官网](http://zookeeper.apache.org/) - - [ZooKeeper 官方文档](https://cwiki.apache.org/confluence/display/ZOOKEEPER) - - [ZooKeeper Github](https://github.com/apache/zookeeper) -- **书籍** - - [《Hadoop 权威指南(第四版)》](https://item.jd.com/12109713.html) -- **文章** - - [分布式服务框架 ZooKeeper -- 管理分布式环境中的数据](https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.html) - - [ZooKeeper 的功能以及工作原理](https://www.cnblogs.com/felixzh/p/5869212.html) - - [ZooKeeper 简介及核心概念](https://github.com/heibaiying/BigData-Notes/blob/master/notes/ZooKeeper%E7%AE%80%E4%BB%8B%E5%8F%8A%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5.md) - - [详解分布式协调服务 ZooKeeper](https://draveness.me/zookeeper-chubby) - - [深入浅出 Zookeeper(一) Zookeeper 架构及 FastLeaderElection 机制](http://www.jasongj.com/zookeeper/fastleaderelection/) \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/05.ZooKeeperAcl.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/05.ZooKeeperAcl.md" deleted file mode 100644 index 4f9152701f..0000000000 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/05.ZooKeeperAcl.md" +++ /dev/null @@ -1,165 +0,0 @@ ---- -title: ZooKeeperAcl -date: 2022-02-19 13:27:21 -categories: - - 大数据 - - zookeeper -tags: - - 分布式 - - 大数据 - - ZooKeeper - - ACL -permalink: /pages/4046ce/ ---- - -# ZooKeeper ACL - -> 为了避免存储在 Zookeeper 上的数据被其他程序或者人为误修改,Zookeeper 提供了 ACL(Access Control Lists) 进行权限控制。 -> -> ACL 权限可以针对节点设置相关读写等权限,保障数据安全性。 - -ZooKeeper ACL 提供了以下几种命令行: - -- **getAcl 命令**:获取某个节点的 acl 权限信息。 -- **setAcl 命令**:设置某个节点的 acl 权限信息。 -- **addauth 命令**:输入认证授权信息,注册时输入明文密码,加密形式保存。 - -## ACL 组成 - -Zookeeper 的 acl 通过 **`[scheme:id:permissions]`** 来构成权限列表。 - -- **scheme**:代表采用的某种权限机制,包括 world、auth、digest、ip、super 几种。 - - **world**:默认模式,所有客户端都拥有指定的权限。world 下只有一个 id 选项,就是 anyone,通常组合写法为 `world:anyone:[permissons]`; - - **auth**:只有经过认证的用户才拥有指定的权限。通常组合写法为 `auth:user:password:[permissons]`,使用这种模式时,你需要先进行登录,之后采用 auth 模式设置权限时,`user` 和 `password` 都将使用登录的用户名和密码; - - **digest**:只有经过认证的用户才拥有指定的权限。通常组合写法为 `auth:user:BASE64(SHA1(password)):[permissons]`,这种形式下的密码必须通过 SHA1 和 BASE64 进行双重加密; - - **ip**:限制只有特定 IP 的客户端才拥有指定的权限。通常组成写法为 `ip:182.168.0.168:[permissions]`; - - **super**:代表超级管理员,拥有所有的权限,需要修改 Zookeeper 启动脚本进行配置。 -- **id**:代表允许访问的用户。 -- **permissions**:权限组合字符串,由 cdrwa 组成,其中每个字母代表支持不同权限。可选项如下: - - **CREATE**:允许创建子节点; - - **READ**:允许从节点获取数据并列出其子节点; - - **WRITE**:允许为节点设置数据; - - **DELETE**:允许删除子节点; - - **ADMIN**:允许为节点设置权限。 - -## 设置与查看权限 - -想要给某个节点设置权限 (ACL),有以下两个可选的命令: - -```bash - # 1.给已有节点赋予权限 - setAcl path acl - - # 2.在创建节点时候指定权限 - create [-s] [-e] path data acl -``` - -查看指定节点的权限命令如下: - -```bash -getAcl path -``` - -## 添加认证信息 - -可以使用如下所示的命令为当前 Session 添加用户认证信息,等价于登录操作。 - -```bash -# 格式 -addauth scheme auth - -#示例:添加用户名为test,密码为root的用户认证信息 -addauth digest test:root -``` - -## 权限设置示例 - -### world 模式 - -world 是一种默认的模式,即创建时如果不指定权限,则默认的权限就是 world。 - -```bash -[zk: localhost:2181(CONNECTED) 32] create /mytest abc -Created /mytest -[zk: localhost:2181(CONNECTED) 4] getAcl /mytest -'world,'anyone # 默认的权限 -: cdrwa -[zk: localhost:2181(CONNECTED) 34] setAcl /mytest world:anyone:cwda # 修改节点,不允许所有客户端读 -.... -[zk: localhost:2181(CONNECTED) 6] get /mytest -org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /mytest # 无权访问 -``` - -### auth 模式 - -```bash -[zk: localhost:2181(CONNECTED) 36] addauth digest test:root # 登录 -[zk: localhost:2181(CONNECTED) 37] setAcl /mytest auth::cdrwa # 设置权限 -[zk: localhost:2181(CONNECTED) 38] getAcl /mytest # 查看权限信息 -'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= # 用户名和密码 (密码经过加密处理),注意返回的权限类型是 digest -: cdrwa - -# 用户名和密码都是使用登录的用户名和密码,即使你在创建权限时候进行指定也是无效的 -[zk: localhost:2181(CONNECTED) 39] setAcl /mytest auth:root:root:cdrwa #指定用户名和密码为 root -[zk: localhost:2181(CONNECTED) 40] getAcl /mytest -'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= #无效,使用的用户名和密码依然还是 test -: cdrwa -``` - -### digest 模式 - -```bash -[zk:44] create /spark "spark" digest:heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s=:cdrwa #指定用户名和加密后的密码 -[zk:45] getAcl /spark #获取权限 -'digest,'heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s= # 返回的权限类型是 digest -: cdrwa -``` - -到这里你可以发现使用 `auth` 模式设置的权限和使用 `digest` 模式设置的权限,在最终结果上,得到的权限模式都是 `digest`。某种程度上,你可以把 `auth` 模式理解成是 `digest` 模式的一种简便实现。因为在 `digest` 模式下,每次设置都需要书写用户名和加密后的密码,这是比较繁琐的,采用 `auth` 模式就可以避免这种麻烦。 - -### ip 模式 - -限定只有特定的 ip 才能访问。 - -```bash -[zk: localhost:2181(CONNECTED) 46] create /hive "hive" ip:192.168.0.108:cdrwa -[zk: localhost:2181(CONNECTED) 47] get /hive -Authentication is not valid : /hive # 当前主机已经不能访问 -``` - -这里可以看到当前主机已经不能访问,想要能够再次访问,可以使用对应 IP 的客户端,或使用下面介绍的 `super` 模式。 - -### super 模式 - -需要修改启动脚本 `zkServer.sh`,并在指定位置添加超级管理员账户和密码信息: - -```bash -"-Dzookeeper.DigestAuthenticationProvider.superDigest=heibai:sCxtVJ1gPG8UW/jzFHR0A1ZKY5s=" -``` - -修改完成后需要使用 `zkServer.sh restart` 重启服务,此时再次访问限制 IP 的节点: - -```bash -[zk: localhost:2181(CONNECTED) 0] get /hive #访问受限 -Authentication is not valid : /hive -[zk: localhost:2181(CONNECTED) 1] addauth digest heibai:heibai # 登录 (添加认证信息) -[zk: localhost:2181(CONNECTED) 2] get /hive #成功访问 -hive -cZxid = 0x158 -ctime = Sat May 25 09:11:29 CST 2019 -mZxid = 0x158 -mtime = Sat May 25 09:11:29 CST 2019 -pZxid = 0x158 -cversion = 0 -dataVersion = 0 -aclVersion = 0 -ephemeralOwner = 0x0 -dataLength = 4 -numChildren = 0 -``` - -## 参考资料 - -- [Zookeeper 安装](https://www.w3cschool.cn/zookeeper/zookeeper_installation.html) -- [Zookeeper 单机环境和集群环境搭建](https://github.com/heibaiying/BigData-Notes/blob/master/notes/installation/Zookeeper%E5%8D%95%E6%9C%BA%E7%8E%AF%E5%A2%83%E5%92%8C%E9%9B%86%E7%BE%A4%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA.md) -- [Zookeeper 客户端基础命令使用](https://www.runoob.com/w3cnote/zookeeper-bs-command.html) \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/README.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/README.md" deleted file mode 100644 index efe0f2e0df..0000000000 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/04.zookeeper/README.md" +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: ZooKeeper 教程 -date: 2020-09-09 17:53:08 -categories: - - 大数据 - - zookeeper -tags: - - 分布式 - - 大数据 - - ZooKeeper -permalink: /pages/1b41b6/ -hidden: true ---- - -# ZooKeeper 教程 - -> ZooKeeper 是 Apache 的顶级项目。**ZooKeeper 为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面,ZooKeeper 并没有直接采用 Paxos 算法,而是采用了名为 ZAB 的一致性协议**。 -> -> ZooKeeper 主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来**维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理**。 -> -> 很多大名鼎鼎的框架都基于 ZooKeeper 来实现分布式高可用,如:Dubbo、Kafka 等。 -> -> ZooKeeper 官方支持 Java 和 C 的 Client API。ZooKeeper 社区为大多数语言(.NET,python 等)提供非官方 API。 - -## 📖 内容 - -### [ZooKeeper 原理](01.ZooKeeper原理.md) - -### [ZooKeeper 命令](02.ZooKeeper命令.md) - -### [ZooKeeper 运维](03.ZooKeeper运维.md) - -### [ZooKeeper Java API](04.ZooKeeperJavaApi.md) - -### [ZooKeeper ACL](05.ZooKeeperAcl.md) - -## 📚 资料 - -- **官方** - - [ZooKeeper 官网](http://zookeeper.apache.org/) - - [ZooKeeper 官方文档](https://cwiki.apache.org/confluence/display/ZOOKEEPER) - - [ZooKeeper Github](https://github.com/apache/zookeeper) - - [Apache Curator 官网](http://curator.apache.org/) -- **书籍** - - [《Hadoop 权威指南(第四版)》](https://item.jd.com/12109713.html) - - [《从 Paxos 到 Zookeeper 分布式一致性原理与实践》](https://item.jd.com/11622772.html) -- **文章** - - [分布式服务框架 ZooKeeper -- 管理分布式环境中的数据](https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/index.html) - - [ZooKeeper 的功能以及工作原理](https://www.cnblogs.com/felixzh/p/5869212.html) - - [ZooKeeper 简介及核心概念](https://github.com/heibaiying/BigData-Notes/blob/master/notes/ZooKeeper%E7%AE%80%E4%BB%8B%E5%8F%8A%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5.md) - - [详解分布式协调服务 ZooKeeper](https://draveness.me/zookeeper-chubby) - - [深入浅出 Zookeeper(一) Zookeeper 架构及 FastLeaderElection 机制](http://www.jasongj.com/zookeeper/fastleaderelection/) - - [Introduction to Apache ZooKeeper](https://www.slideshare.net/sauravhaloi/introduction-to-apache-zookeeper) - - [Zookeeper 的优缺点](https://blog.csdn.net/wwwsq/article/details/7644445) - -## 🚪 传送 - -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/11.spark/01.Spark\347\256\200\344\273\213.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/11.spark/01.Spark\347\256\200\344\273\213.md" index 7abd2eab31..ed843b09b7 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/11.spark/01.Spark\347\256\200\344\273\213.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/11.spark/01.Spark\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: Spark 简介 date: 2019-05-07 20:19:25 +order: 01 categories: - 大数据 - spark diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/01.Flink\345\205\245\351\227\250.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/01.Flink\345\205\245\351\227\250.md" index d7e18b4a57..663b5f464c 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/01.Flink\345\205\245\351\227\250.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/01.Flink\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: Flink 入门 date: 2020-06-22 00:22:25 +order: 01 categories: - 大数据 - flink @@ -27,7 +28,7 @@ permalink: /pages/cf625d/ - **无界流** 有定义流的开始,但没有定义流的结束。它们会无休止地产生数据。无界流的数据必须持续处理,即数据被摄取后需要立刻处理。我们不能等到所有数据都到达再处理,因为输入是无限的,在任何时候输入都不会完成。处理无界数据通常要求以特定顺序摄取事件,例如事件发生的顺序,以便能够推断结果的完整性。 - **有界流** 有定义流的开始,也有定义流的结束。有界流可以在摄取所有数据后再进行计算。有界流所有数据可以被排序,所以并不需要有序摄取。有界流处理通常被称为批处理。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200603134807.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200603134807.png) ### 核心概念 @@ -42,7 +43,7 @@ permalink: /pages/cf625d/ 只有在每一个单独的事件上进行转换操作的应用才不需要状态,换言之,每一个具有一定复杂度的流处理应用都是有状态的。任何运行基本业务逻辑的流处理应用都需要在一定时间内存储所接收的事件或中间结果,以供后续的某个时间点(例如收到下一个事件或者经过一段特定时间)进行访问并进行后续处理。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200603135129.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200603135129.png) 应用状态是 Flink 中的一等公民,Flink 提供了许多状态管理相关的特性支持,其中包括: diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/02.Flink\347\256\200\344\273\213.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/02.Flink\347\256\200\344\273\213.md" index 5c530a1717..194fa907fe 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/02.Flink\347\256\200\344\273\213.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/02.Flink\347\256\200\344\273\213.md" @@ -1,6 +1,7 @@ --- title: Flink简介 date: 2022-02-17 22:28:55 +order: 02 categories: - 大数据 - flink diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/03.FlinkETL.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/03.FlinkETL.md" index 966c9c018c..805b596b45 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/03.FlinkETL.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/03.FlinkETL.md" @@ -1,6 +1,7 @@ --- title: Flink ETL date: 2022-02-17 22:28:55 +order: 03 categories: - 大数据 - flink diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/04.Flink\344\272\213\344\273\266\351\251\261\345\212\250.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/04.Flink\344\272\213\344\273\266\351\251\261\345\212\250.md" index ebe1b4d62c..60c2fa6df7 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/04.Flink\344\272\213\344\273\266\351\251\261\345\212\250.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/04.Flink\344\272\213\344\273\266\351\251\261\345\212\250.md" @@ -1,6 +1,7 @@ --- title: Flink 事件驱动 date: 2022-02-17 22:28:55 +order: 04 categories: - 大数据 - flink diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/05.FlinkApi.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/05.FlinkApi.md" index 354cf04e0f..a465672adb 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/05.FlinkApi.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/05.FlinkApi.md" @@ -1,6 +1,7 @@ --- title: Flink API date: 2022-02-17 22:28:55 +order: 05 categories: - 大数据 - flink diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/06.Flink\346\236\266\346\236\204.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/06.Flink\346\236\266\346\236\204.md" index f99b8c4247..e547c79566 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/06.Flink\346\236\266\346\236\204.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/06.Flink\346\236\266\346\236\204.md" @@ -1,6 +1,7 @@ --- title: Flink 架构 date: 2022-02-17 22:28:55 +order: 06 categories: - 大数据 - flink diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/07.Flink\350\277\220\347\273\264.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/07.Flink\350\277\220\347\273\264.md" index b8405f0913..4e4146e9b8 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/07.Flink\350\277\220\347\273\264.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/07.Flink\350\277\220\347\273\264.md" @@ -1,6 +1,7 @@ --- title: Flink 运维 date: 2022-02-21 09:44:33 +order: 07 categories: - 大数据 - flink diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/08.FlinkTableApi.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/08.FlinkTableApi.md" index db4b1001de..516d755732 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/08.FlinkTableApi.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/08.FlinkTableApi.md" @@ -1,6 +1,7 @@ --- title: Flink Table API & SQL date: 2022-03-18 14:33:03 +order: 08 categories: - 大数据 - flink diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/README.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/README.md" index f5efb09a34..5b00af6a28 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/README.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/13.flink/README.md" @@ -9,6 +9,7 @@ tags: - Flink permalink: /pages/5c85bd/ hidden: true +index: false --- # Flink 教程 @@ -44,4 +45,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/01.flume.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/01.flume.md" index 73d4f34139..6014cdd396 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/01.flume.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/01.flume.md" @@ -1,6 +1,7 @@ --- title: Flume date: 2019-05-07 20:19:25 +order: 01 categories: - 大数据 - 其他 diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/02.sqoop.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/02.sqoop.md" index 7230521baa..44c52e4a0d 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/02.sqoop.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/99.\345\205\266\344\273\226/02.sqoop.md" @@ -1,6 +1,7 @@ --- title: sqoop date: 2020-09-09 17:53:08 +order: 02 categories: - 大数据 - 其他 @@ -39,7 +40,7 @@ permalink: /pages/773408/ #### Sqoop 1 优缺点 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/Sqoop/sqoop-architecture.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/Sqoop/sqoop-architecture.png) 优点 @@ -57,7 +58,7 @@ permalink: /pages/773408/ #### Sqoop 2 优缺点 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/Sqoop/sqoop-v2-architecture.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/Sqoop/sqoop-v2-architecture.png) 优点 @@ -77,8 +78,8 @@ permalink: /pages/773408/ ### 导入 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/Sqoop/sqoop-import.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/Sqoop/sqoop-import.png) ### 导出 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/Sqoop/sqoop-export.png) \ No newline at end of file +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/Sqoop/sqoop-export.png) \ No newline at end of file diff --git "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/README.md" "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/README.md" index ce11bcb92f..4b741b62e2 100644 --- "a/source/_posts/16.\345\244\247\346\225\260\346\215\256/README.md" +++ "b/source/_posts/16.\345\244\247\346\225\260\346\215\256/README.md" @@ -7,6 +7,7 @@ tags: - 大数据 permalink: /pages/fc832f/ hidden: true +index: false ---

@@ -59,38 +60,18 @@ hidden: true - [Hive DML](02.hive/06.HiveDML.md) - [Hive 运维](02.hive/07.Hive运维.md) -### [HBASE](03.hbase) - -- [HBase 原理](03.hbase/01.HBase原理.md) -- [HBase 命令](03.hbase/02.HBase命令.md) -- [HBase 运维](03.hbase/03.HBase运维.md) - -### [ZooKeeper](04.zookeeper) - -> ZooKeeper 是 Apache 的顶级项目。**ZooKeeper 为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面,ZooKeeper 并没有直接采用 Paxos 算法,而是采用了名为 ZAB 的一致性协议**。 -> -> ZooKeeper 主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 ZooKeeper 并不是用来专门存储数据的,它的作用主要是用来**维护和监控存储数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理**。 -> -> 很多大名鼎鼎的框架都基于 ZooKeeper 来实现分布式高可用,如:Dubbo、Kafka 等。 - -- [ZooKeeper 原理](04.zookeeper/01.ZooKeeper原理.md) -- [ZooKeeper 命令](04.zookeeper/02.ZooKeeper命令.md) -- [ZooKeeper 运维](04.zookeeper/03.ZooKeeper运维.md) -- [ZooKeeper Java API](04.zookeeper/04.ZooKeeperJavaApi.md) -- [ZooKeeper ACL](04.zookeeper/05.ZooKeeperAcl.md) - ### Kafka -> **[Kafka](https://dunwu.github.io/blog/pages/328f1c/) 是一个分布式流处理平台,此外,它也被广泛应用于消息队列**。 +> **[Kafka](https://dunwu.github.io/waterdrop/pages/328f1c/) 是一个分布式流处理平台,此外,它也被广泛应用于消息队列**。 -- [Kafka 快速入门](https://dunwu.github.io/blog/pages/a697a6/) -- [Kafka 生产者](https://dunwu.github.io/blog/pages/141b2e/) -- [Kafka 消费者](https://dunwu.github.io/blog/pages/41a171/) -- [Kafka 集群](https://dunwu.github.io/blog/pages/fc8f54/) -- [Kafka 可靠传输](https://dunwu.github.io/blog/pages/481bdd/) -- [Kafka 存储](https://dunwu.github.io/blog/pages/8de948/) -- [Kafka 流式处理](https://dunwu.github.io/blog/pages/55f66f/) -- [Kafka 运维](https://dunwu.github.io/blog/pages/21011e/) +- [Kafka 快速入门](https://dunwu.github.io/waterdrop/pages/a697a6/) +- [Kafka 生产者](https://dunwu.github.io/waterdrop/pages/141b2e/) +- [Kafka 消费者](https://dunwu.github.io/waterdrop/pages/41a171/) +- [Kafka 集群](https://dunwu.github.io/waterdrop/pages/fc8f54/) +- [Kafka 可靠传输](https://dunwu.github.io/waterdrop/pages/481bdd/) +- [Kafka 存储](https://dunwu.github.io/waterdrop/pages/8de948/) +- [Kafka 流式处理](https://dunwu.github.io/waterdrop/pages/55f66f/) +- [Kafka 运维](https://dunwu.github.io/waterdrop/pages/21011e/) ## 📚 资料 @@ -146,4 +127,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\345\205\245\351\227\250.md" "b/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\345\205\245\351\227\250.md" index 44a7af2b65..362c6e2c77 100644 --- "a/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\345\205\245\351\227\250.md" +++ "b/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\345\205\245\351\227\250.md" @@ -1,6 +1,7 @@ --- title: 软件工程入门指南 date: 2017-11-20 17:35:00 +order: 01 categories: - 软件工程 tags: @@ -87,7 +88,7 @@ permalink: /pages/c6742e/ - 软件测试 - 测试的过程分单元测试、组装测试以及系统测试三个阶段进行。测试的方法主要有白盒测试和黑盒测试两种。 - 维护 -![软件生命周期](https://raw.githubusercontent.com/dunwu/images/dev/cs/software-engineering/软件生命周期.gif) +![软件生命周期](https://raw.githubusercontent.com/dunwu/images/master/cs/software-engineering/软件生命周期.gif) ## 软件生命周期模型 @@ -95,7 +96,7 @@ permalink: /pages/c6742e/ > 瀑布模型(Waterfall Model)强调系统开发应有完整的周期,且必须完整的经历周期的每一开发阶段,并系统化的考量分析与设计的技术、时间与资源之投入等。 -![瀑布模型](https://raw.githubusercontent.com/dunwu/images/dev/cs/software-engineering/瀑布模型.jpg) +![瀑布模型](https://raw.githubusercontent.com/dunwu/images/master/cs/software-engineering/瀑布模型.jpg) #### 瀑布模型思想 @@ -125,7 +126,7 @@ permalink: /pages/c6742e/ > 螺旋模型基本做法是在“瀑布模型”的每一个开发阶段前引入一个非常严格的风险识别、风险分析和风险控制,它把软件项目分解成一个个小项目。每个小项目都标识一个或多个主要风险,直到所有的主要风险因素都被确定。 -![螺旋模型](https://raw.githubusercontent.com/dunwu/images/dev/cs/software-engineering/螺旋模型.png) +![螺旋模型](https://raw.githubusercontent.com/dunwu/images/master/cs/software-engineering/螺旋模型.png) #### 螺旋模型思想 diff --git "a/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/README.md" "b/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/README.md" index 3343ff4ba2..a98dfaa620 100644 --- "a/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/README.md" +++ "b/source/_posts/21.\350\275\257\344\273\266\345\267\245\347\250\213/README.md" @@ -7,6 +7,7 @@ tags: - 软件工程 permalink: /pages/40d1d0/ hidden: true +index: false --- # 软件工程 @@ -22,4 +23,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/01.\346\225\210\347\216\207\346\217\220\345\215\207\346\226\271\346\263\225\350\256\272.md" "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/01.\346\225\210\347\216\207\346\217\220\345\215\207\346\226\271\346\263\225\350\256\272.md" index 0f2cccadf2..aa491fd73e 100644 --- "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/01.\346\225\210\347\216\207\346\217\220\345\215\207\346\226\271\346\263\225\350\256\272.md" +++ "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/01.\346\225\210\347\216\207\346\217\220\345\215\207\346\226\271\346\263\225\350\256\272.md" @@ -1,6 +1,7 @@ --- title: 效率提升方法论 date: 2020-02-10 16:00:00 +order: 01 categories: - 工作 - 效能 @@ -35,7 +36,7 @@ permalink: /pages/c33173/ | 三段法 | | 完成状态 | 态度明了
有条不紊 | 灵活性差,只能电子版或软件实现编辑,
纸质版事项会重复 | | OKR 法 | | 目标分解 | 目标导向
高效成事 | 适合复杂事情或大项目的分解执行跟进 | | 分类法 | | 八个方面 | 事事周全
面面俱到 | 越是想顾周全,越难周全,
面面俱到,也会事事难完成 | -| 四象限法 | img | 轻重缓急 | 要事优先
忽略次要 | 被要事牵着走,忽略了
人生应该适度娱乐的重要性 | +| 四象限法 | img | 轻重缓急 | 要事优先
忽略次要 | 被要事牵着走,忽略了
人生应该适度娱乐的重要性 | | 甘特图法 | | 日期进度 | 进度直观
易于理解 | 进度条只能反映时间进度,
无法反映事项具体完成情况的进度 | | PDCA 法 | | 流程顺序 | 流程推进
循环解决 | 流程化容易形成思维惯性,
并且缺乏压力难以形成创造性 | @@ -47,7 +48,7 @@ permalink: /pages/c33173/ 5W2H 分析法的意义在于:避免遇到一个问题后,不知从何入手。通过设问方式,由点成线,由线成面,把问题的关键点串联起来,整理出问题的解决思路。 -![5W2H](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200210161837.png) +![5W2H](https://raw.githubusercontent.com/dunwu/images/master/snap/20200210161837.png) - **why** - 为什么?为什么要这么做?理由何在?原因是什么? - **what** - 是什么?目的是什么?作什么工作? @@ -67,7 +68,7 @@ permalink: /pages/c33173/ 时间管理四象限法则是美国的管理学家科维提出的一个时间管理的理论,按处理顺序划分为:紧急又重要、重要不紧急、紧急不重要、不紧急不重要。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200210173335.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200210173335.png) - **第一象限(重要而紧急**) diff --git "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/03.\350\257\235\346\234\257.md" "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/03.\350\257\235\346\234\257.md" index 5f8bfafa8e..897fddd815 100644 --- "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/03.\350\257\235\346\234\257.md" +++ "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/01.\346\226\271\346\263\225\350\256\272/03.\350\257\235\346\234\257.md" @@ -1,6 +1,7 @@ --- title: 话术 date: 2022-07-11 09:12:29 +order: 03 categories: - 工作 - 效能 diff --git "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/01.\346\212\200\346\234\257\346\226\207\346\241\243\350\247\204\350\214\203.md" "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/01.\346\212\200\346\234\257\346\226\207\346\241\243\350\247\204\350\214\203.md" index 9e24167e79..9c4579d987 100644 --- "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/01.\346\212\200\346\234\257\346\226\207\346\241\243\350\247\204\350\214\203.md" +++ "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/01.\346\212\200\346\234\257\346\226\207\346\241\243\350\247\204\350\214\203.md" @@ -1,6 +1,7 @@ --- title: 技术文档规范 date: 2018-12-11 10:23:22 +order: 01 categories: - 工作 - 效能 @@ -15,7 +16,7 @@ permalink: /pages/bebc05/ > 文档采用 Markdown 语法书写。 > -> 📚 「参考」Markdown 语法可以参考: +> 📚 “参考”Markdown 语法可以参考: > > - https://github.com/guodongxiaren/README > - https://github.com/tchapi/markdown-cheatsheet @@ -235,11 +236,11 @@ One man’s constant is another man’s variable. — Alan Perlis 一些特殊的强调内容可以按照如下方式书写: -> 🔔 『注意』 +> 🔔 “注意” -> 💡 『提示』 +> 💡 “提示” -> 📚 『参考』 +> 📚 “参考” ## 4. 数值 @@ -541,4 +542,4 @@ $1,000 - [豌豆荚文案风格指南](https://docs.google.com/document/d/1R8lMCPf6zCD5KEA8ekZ5knK77iw9J-vJ6vEopPemqZM/edit), by 豌豆荚 - [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines), by sparanoid - [中文排版需求](http://w3c.github.io/clreq/), by W3C -- [为什么文件名要小写?](http://www.ruanyifeng.com/blog/2017/02/filename-should-be-lowercase.html), by 阮一峰 \ No newline at end of file +- [为什么文件名要小写?](http://www.ruanyifeng.com/blog/2017/02/filename-should-be-lowercase.html), by 阮一峰 diff --git "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/02.\347\233\256\345\275\225\347\256\241\347\220\206\350\247\204\350\214\203.md" "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/02.\347\233\256\345\275\225\347\256\241\347\220\206\350\247\204\350\214\203.md" index dc91a804d8..a98dd9b6ba 100644 --- "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/02.\347\233\256\345\275\225\347\256\241\347\220\206\350\247\204\350\214\203.md" +++ "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/02.\347\233\256\345\275\225\347\256\241\347\220\206\350\247\204\350\214\203.md" @@ -1,6 +1,7 @@ --- title: 个人目录管理规范 date: 2018-12-11 10:23:22 +order: 02 categories: - 工作 - 效能 diff --git "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/03.\344\273\243\347\240\201\345\267\245\347\250\213\350\247\204\350\214\203.md" "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/03.\344\273\243\347\240\201\345\267\245\347\250\213\350\247\204\350\214\203.md" index 57d925d653..ada685441e 100644 --- "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/03.\344\273\243\347\240\201\345\267\245\347\250\213\350\247\204\350\214\203.md" +++ "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/02.\350\247\204\350\214\203/03.\344\273\243\347\240\201\345\267\245\347\250\213\350\247\204\350\214\203.md" @@ -1,6 +1,7 @@ --- title: 代码工程规范 date: 2019-03-18 19:26:20 +order: 03 categories: - 工作 - 效能 @@ -145,7 +146,7 @@ logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol); 9. 【强制】日志格式遵循如下格式: -

+
打印出的日志信息如: diff --git "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/99.\345\267\245\345\205\267/01.Markdown.md" "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/99.\345\267\245\345\205\267/01.Markdown.md" index d148eec1fa..852ae809d6 100644 --- "a/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/99.\345\267\245\345\205\267/01.Markdown.md" +++ "b/source/_posts/96.\345\267\245\344\275\234/01.\346\225\210\350\203\275/99.\345\267\245\345\205\267/01.Markdown.md" @@ -1,6 +1,7 @@ --- title: Markdown date: 2020-01-27 23:41:00 +order: 01 categories: - 工作 - 效能 @@ -90,7 +91,7 @@ Markdown 支持六个级别的标题。 语法: ``` -[钝悟的博客](https://dunwu.github.io/blog/) +[钝悟的博客](https://dunwu.github.io/waterdrop/) ``` - `[]` 中标记链接名。类似 HTML 中 `` 元素的 `title` 属性。 @@ -98,7 +99,7 @@ Markdown 支持六个级别的标题。 效果: -- [钝悟的博客](https://dunwu.github.io/blog/ 'blog') +- [钝悟的博客](https://dunwu.github.io/waterdrop/ 'blog') ### 图片 @@ -115,13 +116,13 @@ alt 和 title 即对应 HTML 中 img 元素的 alt 和 title 属性(都可省 - url - 即图片的 url 地址 -![logo](https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo.png 'logo') +![logo](https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png 'logo') ### 图片链接 可以将图片和链接混合使用。 -[![logo](https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo.png 'logo')](https://dunwu.github.io/blog/) +[![logo](https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png 'logo')](https://dunwu.github.io/waterdrop/) ### 锚点 @@ -163,7 +164,7 @@ alt 和 title 即对应 HTML 中 img 元素的 alt 和 title 属性(都可省 ### 代码块 -语法一:在文本前后都使用三个反引号进行标记。【✔️ 推荐】 +语法一:在文本前后都使用三个反引号进行标记。【✔️️️️ 推荐】 ``` 这是一个文本块。 @@ -374,7 +375,7 @@ gantt ### 图片尺寸 -
+
## 编辑器 diff --git "a/source/_posts/96.\345\267\245\344\275\234/README.md" "b/source/_posts/96.\345\267\245\344\275\234/README.md" index 20d7e2458f..cb2bca4d42 100644 --- "a/source/_posts/96.\345\267\245\344\275\234/README.md" +++ "b/source/_posts/96.\345\267\245\344\275\234/README.md" @@ -7,6 +7,7 @@ tags: - 工作 permalink: /pages/1cd051/ hidden: true +index: false --- # DevOps @@ -30,4 +31,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ \ No newline at end of file +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/01.Java/01.\347\216\251\350\275\254Spring\345\205\250\345\256\266\346\241\266\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/01.Java/01.\347\216\251\350\275\254Spring\345\205\250\345\256\266\346\241\266\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..8a0d8480fd --- /dev/null +++ "b/source/_posts/99.\347\254\224\350\256\260/01.Java/01.\347\216\251\350\275\254Spring\345\205\250\345\256\266\346\241\266\347\254\224\350\256\260.md" @@ -0,0 +1,2561 @@ +--- +title: 《玩转 Spring 全家桶》笔记 +date: 2023-07-29 15:25:09 +order: 01 +categories: + - 笔记 + - Java +tags: + - Java + - 框架 + - Spring + - SpringBoot + - SpringCloud +permalink: /pages/eb5c76/ +--- + +# 《玩转 Spring 全家桶》笔记 + +## 第一章:初识 Spring (4 讲) + +### 01 | Spring 课程介绍 + +### 02 | 一起认识 Spring 家族的主要成员 + +Spring Framework - 用于构建企业级应用的轻量级一站式解决方案 + +Spring Boot - 快速构建基于 Spring 的应用程序 + +Spring Cloud - 简化分布式系统的开发 + +### 03 | 跟着 Spring 了解技术趋势 + +略 + +### 04 | 编写你的第一个 Spring 程序 + +略 + +## 第二章:JDBC 必知必会 (10 讲) + +### 05 | 如何配置单数据源 + +#### 直接配置所需的 Bean + +数据源相关 + +- DataSource(根据选择的连接池实现决定) + +事务相关(可选) + +- PlatformTransactionManager(DataSourceTransactionManager) +- TransactionTemplate + +操作相关(可选) + +- JdbcTemplate + +#### Spring Boot 做了哪些配置 + +DataSourceAutoConfiguration + +- 配置 DataSource + +DataSourceTransactionManagerAutoConfiguration + +- 配置 DataSourceTransactionManager + +JdbcTemplateAutoConfiguration + +- 配置 JdbcTemplate + +符合条件时才进行配置 + +#### 数据源相关配置属性 + +通用 + +- `spring.datasource.url=jdbc:mysql://localhost/test` +- `spring.datasource.username=dbuser` +- `spring.datasource.password=dbpass` +- `spring.datasource.driver-class-name=com.mysql.jdbc.Driver`(可选) + +初始化内嵌数据库 + +- `spring.datasource.initialization-mode=embedded|always|never` +- spring.datasource.schema 与 spring.datasource.data 确定初始化 SQL ⽂文件 +- `spring.datasource.platform=hsqldb | h2 | oracle | mysql | postgresql`(与前者对应) + +### 06 | 如何配置多数据源 + +#### 配置多数据源的注意事项 + +不同数据源的配置要分开 + +关注每次使用的数据源 + +- 有多个 DataSource 时系统如何判断 +- 对应的设施(事务、ORM 等)如何选择 DataSource + +#### Spring Boot 中的多数据源配置 + +手工配置两组 DataSource 及相关内容 + +与 Spring Boot 协同工作(二选一) + +- 配置@Primary 类型的 Bean +- 排除 Spring Boot 的自动配置 +- DataSourceAutoConfiguration +- DataSourceTransactionManagerAutoConfiguration +- JdbcTemplateAutoConfiguration + +### 07 | 那些好用的连接池们:HikariCP + +#### 在 Spring Boot 中的配置 + +Spring Boot 2.x + +- 默认使用 HikariCP +- 配置 spring.datasource.hikari.\* 配置 + +Spring Boot 1.x + +- 默认使用 Tomcat 连接池,需要移除 tomcat-jdbc 依赖 +- spring.datasource.type=com.zaxxer.hikari.HikariDataSource + +#### 常用 HikariCP 配置参数 + +常用配置 + +- spring.datasource.hikari.maximumPoolSize=10 +- spring.datasource.hikari.minimumIdle=10 +- spring.datasource.hikari.idleTimeout=600000 +- spring.datasource.hikari.connectionTimeout=30000 +- spring.datasource.hikari.maxLifetime=1800000 + +其他配置详见 HikariCP 官网 + +- https://github.com/brettwooldridge/HikariCP + +### 08 | 那些好用的连接池们:Alibaba Druid + +#### 数据源配置 + +直接配置 DruidDataSource + +通过 druid-spring-boot-starter + +- `spring.datasource.druid.*` + +Filter 配置 + +- spring.datasource.druid.filters=stat,config,wall,log4j (全部使用默认值) + +密码加密 + +- `spring.datasource.password=<加密密码>` +- `spring.datasource.druid.filter.config.enabled=true` +- `spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=` + +SQL 防注入 + +- `spring.datasource.druid.filter.wall.enabled=true` +- `spring.datasource.druid.filter.wall.db-type=h2` +- `spring.datasource.druid.filter.wall.config.delete-allow=false` +- `spring.datasource.druid.filter.wall.config.drop-table-allow=false` + +#### Druid Filter + +- 用于定制连接池操作的各种环节 +- 可以继承 FilterEventAdapter 以便方便地实现 Filter +- 修改 META-INF/druid-filter.properties 增加 Filter 配置 + +### 09 | 如何通过 Spring JDBC 访问数据库 + +#### Spring 的 JDBC 操作类 + +spring-jdbc + +- core,JdbcTemplate 等相关核心接口和类 +- datasource,数据源相关的辅助类 +- object,将基本的 JDBC 操作封装成对象 +- support,错误码等其他辅助工具 + +#### 常用的 Bean 注解 + +通过注解定义 Bean + +- `@Component` +- `@Repository` +- `@Service` +- `@Controller` +- `@RestController` + +#### 简单的 JDBC 操作 + +`JdbcTemplate` + +- `query` +- `queryForObject` +- `queryForList` +- `update` +- `execute` + +#### SQL 批处理 + +`JdbcTemplate` + +- `batchUpdate` +- `BatchPreparedStatementSetter` + +`NamedParameterJdbcTemplate` + +- `batchUpdate` +- `SqlParameterSourceUtils.createBatch` + +### 10 | 什么是 Spring 的事务抽象(上) + +### 11 | 什么是 Spring 的事务抽象(下) + +#### Spring 的事务抽象 + +一致的事务模型 + +- JDBC/Hibernate/myBatis +- DataSource/JTA + +#### 事务抽象的核心接口 + +PlatformTransactionManager + +- DataSourceTransactionManager +- HibernateTransactionManager +- JtaTransactionManager + +TransactionDefinition + +- Propagation +- Isolation +- Timeout +- Read-only status + +#### 事务传播特性 + +| 传播性 | 值 | 描述 | +| ------------------------- | --- | ------------------------------------ | +| PROPAGATION_REQUIRED | 0 | 当前有事务就用当前的,没有就用新的 | +| PROPAGATION_SUPPORTS | 1 | 事务可有可无,不是必须的 | +| PROPAGATION_MANDATORY | 2 | 当前一定要有事务,不然就抛异常 | +| PROPAGATION_REQUIRES_NEW | 3 | 无论是否有事务,都起个新的事务 | +| PROPAGATION_NOT_SUPPORTED | 4 | 不支持事务,按非事务方式运行 | +| PROPAGATION_NEVER | 5 | 不支持事务,如果有事务则抛异常 | +| PROPAGATION_NESTED | 6 | 当前有事务就在当前事务里再起一个事务 | + +#### 事务隔离特性 + +| 隔离性 | 值 | 脏读 | 不可重复读 | 幻读 | +| -------------------------- | --- | ---- | ---------- | ---- | +| ISOLATION_READ_UNCOMMITTED | 1 | ✔️️️ | ✔️️️ | ✔️️️ | +| ISOLATION_READ_COMMITTED | 2 | ❌ | ✔️️️ | ✔️️️ | +| ISOLATION_REPEATABLE_READ | 3 | ❌ | ❌ | ✔️️️ | +| ISOLATION_SERIALIZABLE | 4 | ❌ | ❌ | ❌ | + +#### 编程式事务 + +TransactionTemplate + +- TransactionCallback +- TransactionCallbackWithoutResult + +PlatformTransactionManager + +- 可以传入 TransactionDefinition 进行定义 + +#### 声明式事务 + +开启事务注解的方式 + +- `@EnableTransactionManagement` +- `` + +一些配置 + +- `proxyTargetClass` +- `mode` +- `order` + +`@Transactional` + +- `transactionManager` +- `propagation` +- `isolation` +- `timeout` +- `readOnly` +- 怎么判断回滚 + +### 12 | 了解 Spring 的 JDBC 异常抽象 + +#### Spring 的 JDBC 异常抽象 + +Spring 会将数据操作的异常转换为 DataAccessException + +无论使用何种数据访问方式,都能使用一样的异常 + +#### Spring 是怎么认识那些错误码的 + +通过 SQLErrorCodeSQLExceptionTranslator 解析错误码 + +ErrorCode 定义 + +- org/springframework/jdbc/support/sql-error-codes.xml +- Classpath 下的 sql-error-codes.xml + +### 13 | 课程答疑(上) + +略 + +### 14 | 课程答疑(下) + +略 + +## 第三章:O/R Mapping 实践 (9 讲) + +### 15 | 认识 Spring Data JPA + +#### Java Persistence API + +JPA 为对象关系映射提供了一种基于 POJO 的持久化模型 + +- 简化数据持久化代码的开发工作 +- 为 Java 社区屏蔽不同持久化 API 的差异 + +#### Spring Data + +在保留底层存储特性的同时,提供相对一致的、基于 Spring 的编程模型 + +主要模块 + +- Spring Data Commons +- Spring Data JDBC +- Spring Data JPA +- Spring Data Redis +- …… + +### 16 | 定义 JPA 的实体对象 + +#### 常用 JPA 注解 + +实体 + +- @Entity、@MappedSuperclass +- @Table(name) + +主键 + +- @Id +- @GeneratedValue(strategy, generator) +- @SequenceGenerator(name, sequenceName) + +映射 + +- @Column(name, nullable, length, insertable, updatable) +- @JoinTable(name)、@JoinColumn(name) + +关系 + +- @OneToOne、@OneToMany、@ManyToOne、@ManyToMany +- @OrderBy + +#### Lombok + +Project Lombok 能够自动嵌入 IDE 和构建工具,提升开发效率 + +常用功能 + +- @Getter / @Setter +- @ToString +- @NoArgsConstructor / @RequiredArgsConstructor / @AllArgsConstructor +- @Data +- @Builder +- @Slf4j / @CommonsLog / @Log4j2 + +### 17 | 开始我们的线上咖啡馆实战项目:SpringBucks + +略 + +### 18 | 通过 Spring Data JPA 操作数据库 + +#### Repository + +@EnableJpaRepositories + +Repository 接口 + +- CrudRepository +- PagingAndSortingRepository +- JpaRepository + +#### 定义查询 + +根据方法名定义查询 + +- `find…By… / read…By… / query…By… / get…By…` +- `count…By…` +- `…OrderBy…[Asc / Desc]` +- `And / Or / IgnoreCase` +- `Top / First / Distinct` + +#### 分页查询 + +- `PagingAndSortingRepository` +- `Pageable / Sort` +- `Slice / Page` + +### 19 | Spring Data JPA 的 Repository 是怎么从接口变成 Bean 的 + +#### Repository Bean 是如何创建的 + +JpaRepositoriesRegistrar + +- 激活了 @EnableJpaRepositories +- 返回了 JpaRepositoryConfigExtension + +RepositoryBeanDefinitionRegistrarSupport.registerBeanDefinitions + +- 注册 Repository Bean(类型是 JpaRepositoryFactoryBean) + +RepositoryConfigurationExtensionSupport.getRepositoryConfigurations + +- 取得 Repository 配置 + +JpaRepositoryFactory.getTargetRepository + +- 创建了 Repository + +#### 接口中的方法是如何被解释的 + +RepositoryFactorySupport.getRepository 添加了 Advice + +- DefaultMethodInvokingMethodInterceptor +- QueryExecutorMethodInterceptor + +AbstractJpaQuery.execute 执行具体的查询 + +语法解析在 Part 中 + +### 20 | 通过 MyBatis 操作数据库 + +在 Spring 中使用 MyBatis + +- MyBatis Spring Adapter(https://github.com/mybatis/spring) +- MyBatis Spring-Boot-Starter(https://github.com/mybatis/spring-boot-starter) + +简单配置 + +- mybatis.mapper-locations = classpath*:mapper/\*\*/*.xml +- mybatis.type-aliases-package = 类型别名的包名 +- mybatis.type-handlers-package = TypeHandler 扫描包名 +- mybatis.configuration.map-underscore-to-camel-case = true + +Mapper 的定义与扫描 + +- @MapperScan 配置扫描位置 +- @Mapper 定义接口 +- 映射的定义—— XML 与注解 + +### 21 | 让 MyBatis 更好用的那些工具:MyBatis Generator + +MyBatis Generator(http://www.mybatis.org/generator/) + +### 22 | 让 MyBatis 更好用的那些工具:MyBatis PageHelper + +MyBatis PageHepler(https://pagehelper.github.io) + +### 23 | SpringBucks 实战项目进度小结 + +略 + +## 第四章:NoSQL 实践 (7 讲) + +### 24 | 通过 Docker 辅助开发 + +#### Docker 常用命令 + +镜像相关 + +- `docker pull ` +- `docker search ` + +容器相关 + +- `docker run` +- `docker start/stop <容器名>` +- `docker ps <容器名>` +- `docker logs <容器名>` + +#### docker run 的常用选项 + +`docker run [OPTIONS] IMAGE [COMMAND] [ARG…]` + +选项说明 + +- -d,后台运行容器 +- -e,设置环境变量 +- --expose / -p 宿主端口:容器端口 +- --name,指定容器名称 +- --link,链接不同容器 +- -v 宿主目录:容器目录,挂载磁盘卷 + +#### 国内 Docker 镜像配置 + +官方 Docker Hub + +- https://hub.docker.com + +官方镜像 + +- 镜像 https://www.docker-cn.com/registry-mirror +- 下载 https://www.docker-cn.com/get-docker + +阿里云镜像 + +- https://dev.aliyun.com + +### 25 | 在 Spring 中访问 MongoDB + +#### Spring 对 MongoDB 的支持 + +- Spring Data MongoDB + - MongoTemplate + - Repository 支持 + +#### Spring Data MongoDB 的基本用法 + +注解 + +- @Document +- @Id + +MongoTemplate + +- save / remove +- Criteria / Query / Update + +#### Spring Data MongoDB 的 Repository + +`@EnableMongoRepositories` + +对应接口 + +- `MongoRepository` +- `PagingAndSortingRepository` +- `CrudRepository` + +### 26 | 在 Spring 中访问 Redis + +#### Spring 对 Redis 的支持 + +- Spring Data Redis + - 支持的客户端 Jedis / Lettuce + - RedisTemplate + - Repository 支持 + +#### Jedis 客户端的简单使用 + +- Jedis 不是线程安全的 +- 通过 JedisPool 获得 Jedis 实例 +- 直接使用 Jedis 中的方法 + +### 27 | Redis 的哨兵与集群模式 + +- JedisSentinelPool +- JedisCluster + +### 28 | 了解 Spring 的缓存抽象 + +#### Spring 的缓存抽象 + +为不同的缓存提供一层抽象 + +- 为 Java 方法增加缓存,缓存执行结果 +- 支持 ConcurrentMap、EhCache、Caffeine、JCache(JSR-107) +- 接口 + - `org.springframework.cache.Cache` + - `org.springframework.cache.CacheManager` + +#### 基于注解的缓存 + +@EnableCaching + +- @Cacheable +- @CacheEvict +- @CachePut +- @Caching +- @CacheConfig + +### 29 | Redis 在 Spring 中的其他用法 + +#### 与 Redis 建立连接 + +配置连接工厂 + +- LettuceConnectionFactory 与 JedisConnectionFactory + - RedisStandaloneConfiguration + - RedisSentinelConfiguration + - RedisClusterConfiguration + +#### 读写分离 + +Lettuce 内置支持读写分离 + +- 只读主、只读从 +- 优先读主、优先读从 + +LettuceClientConfiguration + +LettucePoolingClientConfiguration + +LettuceClientConfigurationBuilderCustomizer + +#### RedisTemplate + +RedisTemplate + +- opsForXxx() + +StringRedisTemplate + +#### Redis Repository + +实体注解 + +- @RedisHash +- @Id +- @Indexed + +#### 处理不同类型数据源的 Repository + +如何区分这些 Repository + +- 根据实体的注解 +- 根据继承的接口类型 +- 扫描不同的包 + +### 30 | SpringBucks 实战项目进度小结 + +略 + +## 第五章:数据访问进阶 (8 讲) + +### 31 | Project Reactor 介绍(上) + +### 32 | Project Reactor 介绍(下) + +一些核心的概念 + +Operators - Publisher / Subscriber + +- Nothing Happens Until You subscribe() +- Flux [ 0..N ] - onNext()、onComplete()、onError() +- Mono [ 0..1 ] - onNext()、onComplete()、onError() + +Backpressure + +- Subscription +- onRequest()、onCancel()、onDispose() + +线程调度 Schedulers + +- immediate() / single() / newSingle() +- elastic() / parallel() / newParallel() + +错误处理 + +- onError / onErrorReturn / onErrorResume +- doOnError / doFinally + +### 33 | 通过 Reactive 的方式访问 Redis + +#### Spring Data Redis + +Lettuce 能够支持 Reactive 方式 + +Spring Data Redis 中主要的支持 + +- ReactiveRedisConnection +- ReactiveRedisConnectionFactory +- ReactiveRedisTemplate +- opsForXxx() + +### 34 | 通过 Reactive 的方式访问 MongoDB + +#### Spring Data MongoDB + +MongoDB 官方提供了支持 Reactive 的驱动 + +- mongodb-driver-reactivestreams + +Spring Data MongoDB 中主要的支持 + +- ReactiveMongoClientFactoryBean +- ReactiveMongoDatabaseFactory +- ReactiveMongoTemplate + +### 35 | 通过 Reactive 的方式访问 RDBMS + +#### Spring Data R2DBC + +R2DBC (https://spring.io/projects/spring-data-r2dbc) + +- Reactive Relational Database Connectivity + +支持的数据库 + +- Postgres(io.r2dbc:r2dbc-postgresql) +- H2(io.r2dbc:r2dbc-h2) +- Microsoft SQL Server(io.r2dbc:r2dbc-mssql) + +一些主要的类 + +- ConnectionFactory +- DatabaseClient + - execute().sql(SQL) + - inTransaction(db -> {}) +- R2dbcExceptionTranslator + - SqlErrorCodeR2dbcExceptionTranslator + +R2DBC Repository 支持 + +一些主要的类 + +- @EnableR2dbcRepositories +- ReactiveCrudRepository +- @Table / @Id +- 其中的方法返回都是 Mono 或者 Flux +- 自定义查询需要自己写 @Query + +### 36 | 通过 AOP 打印数据访问层的摘要(上) + +### 37 | 通过 AOP 打印数据访问层的摘要(下) + +#### Spring AOP 的一些核心概念 + +| 概念 | 含义 | +| ------------ | ------------------------------------------------------ | +| Aspect | 切面 | +| Join | Point 连接点,Spring AOP 里总是代表一次方法执行 | +| Advice | 通知,在连接点执行的动作 | +| Pointcut | 切入点,说明如何匹配连接点 | +| Introduction | 引入,为现有类型声明额外的方法和属性 | +| Target | object 目标对象 | +| AOP proxy | AOP 代理对象,可以是 JDK 动态代理,也可以是 CGLIB 代理 | +| Weaving | 织入,连接切面与目标对象或类型创建代理的过程 | + +#### 常用注解 + +- @EnableAspectJAutoProxy +- @Aspect +- @Pointcut +- @Before +- @After / @AfterReturning / @AfterThrowing +- @Around +- @Order + +#### 如何打印 SQL + +HikariCP + +- P6SQL,https://github.com/p6spy/p6spy + +Alibaba Druid + +- 内置 SQL 输出 +- https://github.com/alibaba/druid/wiki/Druid中使⽤用log4j2进⾏行行⽇日志输出 + +### 38 | SpringBucks 实战项目进度小结 + +略 + +## 第六章:Spring MVC 实践 (14 讲) + +### 39 | 编写第一个 Spring MVC Controller + +### 认识 Spring MVC + +DispatcherServlet + +- Controller +- xxxResolver +- ViewResolver +- HandlerExceptionResolver +- MultipartResolver +- HandlerMapping + +### Spring MVC 中的常⽤用注解 + +- @Controller +- @RestController +- @RequestMapping +- @GetMapping / @PostMapping +- @PutMapping / @DeleteMapping +- @RequestBody / @ResponseBody / @ResponseStatus + +### 40 | 理解 Spring 的应用上下文 + +### Spring 的应用程序上下文 + +**关于上下文常用的接口** + +- BeanFactory +- DefaultListableBeanFactory +- ApplicationContext +- ClassPathXmlApplicationContext +- FileSystemXmlApplicationContext +- AnnotationConfigApplicationContext +- WebApplicationContext + +### 41 | 理解请求的处理机制 + +一个请求的大致处理流程 + +绑定一些 Attribute + +- WebApplicationContext / LocaleResolver / ThemeResolver + +处理 Multipart + +- 如果是,则将请求转为 MultipartHttpServletRequest + +Handler 处理 + +- 如果找到对应 Handler,执行 Controller 及前后置处理器逻辑处理返回的 Model ,呈现视图 + +### 42 | 如何定义处理方法(上) + +### 定义映射关系 + +@Controller + +@RequestMapping + +- path / method 指定映射路路径与⽅方法 +- params / headers 限定映射范围 +- consumes / produces 限定请求与响应格式 + +一些快捷方式 + +- @RestController +- @GetMapping / @PostMapping / @PutMapping / @DeleteMapping / @PatchMapping + +### 定义处理方法 + +- @RequestBody / @ResponseBody / @ResponseStatus +- @PathVariable / @RequestParam / @RequestHeader +- HttpEntity / ResponseEntity + +### 定义类型转换 + +自己实现 WebMvcConfigurer + +- Spring Boot 在 WebMvcAutoConfiguration 中实现了一个 +- 添加自定义的 Converter +- 添加自定义的 Formatter + +### 定义校验 + +- 通过 Validator 对绑定结果进行校验 + - Hibernate Validator +- @Valid 注解 +- BindingResult + +### Multipart 上传 + +- 配置 MultipartResolver +- Spring Boot 自动配置 MultipartAutoConfiguration +- 支持类型 multipart/form-data +- MultipartFile 类型 + +### 43 | 如何定义处理方法(下) + +### 44 | Spring MVC 中的视图解析机制(上) + +### 45 | Spring MVC 中的视图解析机制(下) + +#### 视图解析的实现基础 + +ViewResolver 与 View 接口 + +- AbstractCachingViewResolver +- UrlBasedViewResolver +- FreeMarkerViewResolver +- ContentNegotiatingViewResolver +- InternalResourceViewResolver + +#### DispatcherServlet 中的视图解析逻辑 + +- initStrategies() + - initViewResolvers() 初始化了了对应 ViewResolver +- doDispatch() + - processDispatchResult() + - 没有返回视图的话,尝试 RequestToViewNameTranslator + - resolveViewName() 解析 View 对象 + +使用 @ResponseBody 的情况 + +- 在 HandlerAdapter.handle() 的中完成了 Response 输出 + - `RequestMappingHandlerAdapter.invokeHandlerMethod()` + - `HandlerMethodReturnValueHandlerComposite.handleReturnValue()` + - `RequestResponseBodyMethodProcessor.handleReturnValue()` + +#### 重定向 + +两种不同的重定向前缀 + +- `redirect:` +- `forward:` + +### 46 | Spring MVC 中的常用视图(上) + +#### Spring MVC 支持的视图 + +支持的视图列表 + +- https://docs.spring.io/spring/docs/5.1.5.RELEASE/spring-frameworkreference/web.html#mvc-view +- Jackson-based JSON / XML +- Thymeleaf & FreeMarker + +#### 配置 MessageConverter + +- 通过 WebMvcConfigurer 的 configureMessageConverters() +- Spring Boot 自动查找 HttpMessageConverters 进行注册 + +#### Spring Boot 对 Jackson 的支持 + +- JacksonAutoConfiguration + - Spring Boot 通过 @JsonComponent 注册 JSON 序列化组件 + - Jackson2ObjectMapperBuilderCustomizer +- JacksonHttpMessageConvertersConfiguration + - 增加 jackson-dataformat-xml 以支持 XML 序列化 + +### 47 | Spring MVC 中的常用视图(下) + +#### 使用 Thymeleaf + +添加 Thymeleaf 依赖 + +- org.springframework.boot:spring-boot-starter-thymeleaf + +Spring Boot 的自动配置 + +- `ThymeleafAutoConfiguration` + - `ThymeleafViewResolver` + +##### Thymeleaf 的一些默认配置 + +- `spring.thymeleaf.cache=true` +- `spring.thymeleaf.check-template=true` +- `spring.thymeleaf.check-template-location=true` +- `spring.thymeleaf.enabled=true` +- `spring.thymeleaf.encoding=UTF-8` +- `spring.thymeleaf.mode=HTML` +- `spring.thymeleaf.servlet.content-type=text/html` +- `spring.thymeleaf.prefix=classpath:/templates/` +- `spring.thymeleaf.suffix=.html` + +### 48 | 静态资源与缓存 + +#### Spring Boot 中的静态资源配置 + +核心逻辑 + +- WebMvcConfigurer.addResourceHandlers() + +常用配置 + +- spring.mvc.static-path-pattern=/\*\* +- spring.resources.static-locations=classpath:/META-INF/ + resources/,classpath:/resources/,classpath:/static/,classpath:/public/ + +#### Spring Boot 中的缓存配置 + +常用配置(默认时间单位都是秒) + +- ResourceProperties.Cache +- spring.resources.cache.cachecontrol.max-age=时间 +- spring.resources.cache.cachecontrol.no-cache=true/false +- spring.resources.cache.cachecontrol.s-max-age=时间 + +### 49 | Spring MVC 中的异常处理机制 + +#### Spring MVC 的异常解析 + +核心接口 + +- HandlerExceptionResolver + +实现类 + +- SimpleMappingExceptionResolver +- DefaultHandlerExceptionResolver +- ResponseStatusExceptionResolver +- ExceptionHandlerExceptionResolver + +#### 异常处理方法 + +处理方法 + +- @ExceptionHandler + +添加位置 + +- @Controller / @RestController +- @ControllerAdvice / @RestControllerAdvice + +### 50 | 了解 Spring MVC 的切入点 + +#### Spring MVC 的拦截器 + +核心接口 + +- HandlerInteceptor + - boolean preHandle() + - void postHandle() + - void afterCompletion() + +针对 @ResponseBody 和 ResponseEntity 的情况 + +- ResponseBodyAdvice + +针对异步请求的接口 + +- AsyncHandlerInterceptor + +#### 拦截器的配置方式 + +常规方法 + +- WebMvcConfigurer.addInterceptors() + +Spring Boot 中的配置 + +- 创建一个带 @Configuration 的 WebMvcConfigurer 配置类 +- 不能带 @EnableWebMvc(想彻底自己控制 MVC 配置除外) + +### 51 | SpringBucks 实战项目进度小结 + +略 + +### 52 | 课程答疑 + +略 + +## 第七章:访问 Web 资源 (5 讲) + +### 53 | 通过 RestTemplate 访问 Web 资源 + +#### Spring Boot 中的 RestTemplate + +- Spring Boot 中没有自动配置 RestTemplate +- Spring Boot 提供了 RestTemplateBuilder +- RestTemplateBuilder.build() + +#### 常用方法 + +GET 请求 + +- getForObject() / getForEntity() + +POST 请求 + +- postForObject() / postForEntity() + +PUT 请求 + +- put() + +DELETE 请求 + +- delete() + +#### 构造 URI + +构造 URI + +- UriComponentsBuilder + +构造相对于当前请求的 URI + +- ServletUriComponentsBuilder + +构造指向 Controller 的 URI + +- MvcUriComponentsBuilder + +### 54 | RestTemplate 的高阶用法 + +传递 HTTP Header + +- `RestTemplate.exchange()` +- `RequestEntity / ResponseEntity` + +类型转换 + +- `JsonSerializer / JsonDeserializer` +- `@JsonComponent` + +解析泛型对象 + +- `RestTemplate.exchange()` +- `ParameterizedTypeReference` + +### 55 | 简单定制 RestTemplate + +#### RestTemplate ⽀支持的 HTTP 库 + +通用接口 + +- ClientHttpRequestFactory + +默认实现 + +- SimpleClientHttpRequestFactory + +Apache HttpComponents + +- HttpComponentsClientHttpRequestFactory + +Netty + +- Netty4ClientHttpRequestFactory + +OkHttp + +- OkHttp3ClientHttpRequestFactory + +#### 优化底层请求策略 + +连接管理 + +- PoolingHttpClientConnectionManager +- KeepAlive 策略 + +超时设置 + +- connectTimeout / readTimeout + +SSL 校验 + +- 证书检查策略 + +### 56 | 通过 WebClient 访问 Web 资源 + +#### 了解 WebClient + +WebClient + +- 一个以 Reactive 方式处理 HTTP 请求的非阻塞式的客户端 + +支持的底层 HTTP 库 + +- Reactor Netty - ReactorClientHttpConnector +- Jetty ReactiveStream HttpClient - JettyClientHttpConnector + +#### WebClient 的基本用法 + +创建 WebClient + +- WebClient.create() +- WebClient.builder() + +发起请求 + +- get() / post() / put() / delete() / patch() + +获得结果 + +- retrieve() / exchange() + +处理 HTTP Status + +- onStatus() + +应答正文 + +- bodyToMono() / bodyToFlux() + +### 57 | SpringBucks 实战项目进度小结 + +## 第八章: Web 开发进阶 (9 讲) + +### 58 | 设计好的 RESTful Web Service(上) + +### 59 | 设计好的 RESTful Web Service(下) + +如何实现 Restful Web Service + +- 识别资源 +- 选择合适的资源粒度 +- 设计 URI +- 选择合适的 HTTP 方法和返回码 +- 设计资源的表述 + +识别资源 + +- 找到领域名词 +- 能用 CRUD 操作的名词 +- 将资源组织为集合(即集合资源) +- 将资源合并为复合资源 +- 计算或处理函数 + +### 资源的粒度 + +站在客户端的角度,要考虑 + +- 可缓存性 +- 修改频率 +- 可变性 + +站在服务端的角度,要考虑 + +- 网络效率 +- 表述的多少 +- 客户端的易用程度 + +构建更好的 URI + +- 使用域及子域对资源进行合理的分组或划分 +- 在 URI 的路径部分使用斜杠分隔符 ( / ) 来表示资源之间的层次关系 +- 在 URI 的路径部分使用逗号 ( , ) 和分号 ( ; ) 来表示非层次元素 +- 使用连字符 ( - ) 和下划线 ( \_ ) 来改善长路径中名称的可读性 +- 在 URI 的查询部分使用“与”符号 ( & ) 来分隔参数 +- 在 URI 中避免出现文件扩展名 ( 例例如 .php,.aspx 和 .jsp ) + +### 60 | 什么是 HATEOAS + +### 61 | 使用 Spring Data REST 实现简单的超媒体服务(上) + +### 62 | 使用 Spring Data REST 实现简单的超媒体服务(下) + +#### 认识 HAL + +HAL + +- Hypertext Application Language +- HAL 是一种简单的格式,为 API 中的资源提供简单一致的链接 + +HAL 模型 + +- 链接 +- 内嵌资源 +- 状态 + +#### Spring Data REST + +Spring Boot 依赖 + +- spring-boot-starter-data-rest + +常用注解与类 + +- `@RepositoryRestResource` +- `Resource` +- `PagedResource` + +#### 如何访问 HATEOAS 服务 + +配置 Jackson JSON + +- 注册 HAL 支持 + +操作超链接 + +- 找到需要的 Link +- 访问超链接 + +### 63 | 分布式环境中如何解决 Session 的问题 + +#### 常见的会话解决方案 + +- 粘性会话 Sticky Session +- 会话复制 Session Replication +- 集中会话 Centralized Session + +#### 认识 Spring Session + +Spring Session + +- 简化集群中的用户会话管理 +- 无需绑定容器特定解决方案 + +支持的存储 + +- Redis +- MongoDB +- JDBC +- Hazelcast + +#### 实现原理 + +定制 HttpSession + +- 通过定制的 HttpServletRequest 返回定制的 HttpSession +- `SessionRepositoryRequestWrapper` +- `SessionRepositoryFilter` +- `DelegatingFilterProxy` + +#### 基于 Redis 的 HttpSession + +引入依赖 + +- spring-session-data-redis + +基本配置 + +- @EnableRedisHttpSession +- 提供 RedisConnectionFactory +- 实现 AbstractHttpSessionApplicationInitializer +- 配置 DelegatingFilterProxy + +### 64 | 使用 WebFlux 代替 Spring MVC(上) + +### 65 | 使用 WebFlux 代替 Spring MVC(下) + +#### 认识 WebFlux + +什么是 WebFlux + +- 用于构建基于 Reactive 技术栈之上的 Web 应用程序 +- 基于 Reactive Streams API ,运行在非阻塞服务器上 + +为什么会有 WebFlux + +- 对于非阻塞 Web 应用的需要 +- 函数式编程 + +关于 WebFlux 的性能 + +- 请求的耗时并不会有很大的改善 +- 仅需少量固定数量的线程和较少的内存即可实现扩展 + +#### WebMVC v.s. WebFlux + +- 已有 Spring MVC 应⽤用,运行正常,就别改了 +- 依赖了大量阻塞式持久化 API 和网络 API,建议使用 Spring MVC +- 已经使用了非阻塞技术栈,可以考虑使用 WebFlux +- 想要使用 Java 8 Lambda 结合轻量级函数式框架,可以考虑 WebFlux + +#### WebFlux 中的编程模型 + +两种编程模型 + +- 基于注解的控制器 +- 函数式 Endpoints + +#### 基于注解的控制器 + +常用注解 + +- @Controller +- @RequestMapping 及其等价注解 +- @RequestBody / @ResponseBody + +返回值 + +- `Mono / Flux` + +### 66 | SpringBucks 实战项目进度小结 + +略 + +## 第九章:重新认识 Spring Boot (8 讲) + +### 67 | 认识 Spring Boot 的组成部分 + +### Spring Boot 的特性 + +- 方便地创建可独立运行的 Spring 应用程序 +- 直接内嵌 Tomcat、Jetty 或 Undertow +- 简化了项目的构建配置 +- 为 Spring 及第三方库提供自动配置 +- 提供生产级特性 +- 无需生成代码或进行 XML 配置 + +### Spring Boot 的四大核心 + +- 自动配置 - Auto Configuration +- 起步依赖 - Starter Dependency +- 命令行界面 - Spring Boot CLI +- Actuator + +### 68 | 了解自动配置的实现原理 + +### 了解自动配置 + +**自动配置** + +- 基于添加的 JAR 依赖自动对 Spring Boot 应⽤用程序进行配置 +- spring-boot-autoconfiguration + +**开启自动配置** + +- `@EnableAutoConfiguration` + - `exclude = Class[]` +- `@SpringBootApplication` + +### 自动配置的实现原理 + +**`@EnableAutoConfiguration`** + +- `AutoConfigurationImportSelector` +- `META-INF/spring.factories` + - `org.springframework.boot.autoconfigure.EnableAutoConfiguration` + +**条件注解** + +- `@Conditional` +- `@ConditionalOnClass` +- `@ConditionalOnBean` +- `@ConditionalOnMissingBean` +- `@ConditionalOnProperty` +- …… + +### 了解自动配置的情况 + +**观察自动配置的判断结果** + +- --debug + +**`ConditionEvaluationReportLoggingListener`** + +- Positive matches +- Negative matches +- Exclusions +- Unconditional classes + +### 69 | 动手实现自己的自动配置 + +### 主要工作内容 + +- 编写 Java Config + - `@Configuration` +- 添加条件 + - `@Conditional` +- 定位自动配置 + - `META-INF/spring.factories` + +### 条件注解 + +**条件注解** + +- `@Conditional` + +**类条件** + +- `@ConditionalOnClass` +- `@ConditionalOnMissingClass` + +**属性条件** + +- `@ConditionalOnProperty` + +**Bean 条件** + +- `@ConditionalOnBean` +- `@ConditionalOnMissingBean` +- `**@ConditionalOnSingleCandidate`\*\* + +**资源条件** + +- `@ConditionalOnResource` + +**Web 应用条件** + +- `@ConditionalOnWebApplication` +- `@ConditionalOnNotWebApplication` + +**其他条件** + +- `@ConditionalOnExpression` +- `@ConditionalOnJava` +- `@ConditionalOnJndi` + +### 自动配置的执行顺序 + +**执行顺序** + +- `@AutoConfigureBefore` +- `@AutoConfigureAfter` +- `@AutoConfigureOrder` + +### 70 | 如何在低版本 Spring 中快速实现类似自动配置的功能 + +### 需求与问题 + +**核心的诉求** + +- 现存系统,不打算重构 +- Spring 版本 3.x,不打算升级版本和引入 Spring Boot +- 期望能够在少改代码的前提下实现一些功能增强 + +**面临的问题** + +- 3.x 的 Spring 没有条件注解 +- 无法自动定位需要加载的自动配置 + +### 核心解决思路 + +**条件判断** + +- 通过 `BeanFactoryPostProcessor` 进行判断 + +**配置加载** + +- 编写 Java Config 类 +- 引入配置类 +- 通过 component-scan +- 通过 xml 文件 import + +### Spring 的扩展点 + +**BeanPostProcessor** + +- 针对 Bean 实例 + +- 在 Bean 创建后提供定制逻辑回调 + +**BeanFactoryPostProcessor** + +- 针对 Bean 定义 + +- 在容器创建 Bean 前获取配置元数据 + +- Java Config 中需要定义为 static 方法 + +### 关于 Bean 的一些定制 + +#### 生命周期回调 + +- InitializingBean / @PostConstruct / init-method +- DisposableBean / @PostDestory / destroy-method + +#### XXXAware + +- `ApplicationContextAware` +- `BeanFactoryAware` +- `BeanNameAware` + +### 一些常用操作 + +**判断类是否存在** + +- `ClassUtils.isPresent()` + +**判断 Bean 是否已定义** + +- `ListableBeanFactory.containsBeanDefinition()` +- `ListableBeanFactory.getBeanNamesForType()` + +**注册 Bean 定义** + +- `BeanDefinitionRegistry.registerBeanDefinition()` + + - `GenericBeanDefinition` + +- `BeanFactory.registerSingleton()` + +### 71 | 了解起步依赖及其实现原理 + +### Maven 依赖管理技巧 + +了解你的依赖 + +- mvn dependency:tree +- IDEA Maven Helper 插件 + +排除特定依赖 + +- exclusion + +统一管理依赖 + +- dependencyManagement +- Bill of Materials - bom + +### Spring Boot 的 starter 依赖 + +**Starter Dependencies** + +- 直接面向功能 +- 一站获得所有相关依赖,不再复制粘贴 + +**官方的 Starters** + +- spring-boot-starter-\* + +### 72 | 定制自己的起步依赖 + +**主要内容** + +- autoconfigure 模块,包含自动配置代码 +- starter 模块,包含指向自动配置模块的依赖及其他相关依赖 + +**命名方式** + +- xxx-spring-boot-autoconfigure +- xxx-spring-boot-starter + +**注意事项** + +- 不要使用 spring-boot 作为依赖的前缀 +- 不要使用 spring-boot 的配置命名空间 +- starter 中仅添加必要的依赖 +- 声明对 spring-boot-starter 的依赖 + +### 73 | 深挖 Spring Boot 的配置加载机制 + +### 外化配置加载顺序 + +- 开启 DevTools 时,`~/.spring-boot-devtools.properties` +- 测试类上的 `@TestPropertySource` 注解 +- `@SpringBootTest#properties` 属性 +- 命令行参数( `--server.port=9000` ) +- SPRING_APPLICATION_JSON 中的属性 +- `ServletConfig` 初始化参数 +- `ServletContext` 初始化参数 +- `java:comp/env` 中的 JNDI 属性 +- `System.getProperties()` +- 操作系统环境变量 +- `random.*` 涉及到的 `RandomValuePropertySource` +- jar 包外部的 application-{profile}.properties 或 .yml +- jar 包内部的 application-{profile}.properties 或 .yml +- jar 包外部的 application.properties 或 .yml +- jar 包内部的 application.properties 或 .yml +- @Configuration 类上的 @PropertySource +- SpringApplication.setDefaultProperties() 设置的默认属性 + +### application.properties + +默认位置 + +- `./config` +- `./` +- CLASSPATH 中的 `/config` +- CLASSPATH 中的 `/` + +修改名字或路路径 + +- `spring.config.name` +- `spring.config.location` +- `spring.config.additional-location` + +### Relaxed Binding + +| 命名风格 | 使用范围 | 示例 | +| -------------------- | ------------------------------------------ | ------------------------------- | +| 短划线分隔 | Properties 文件
YAML 文件
系统属性 | geektime.spring-boot.first-demo | +| 驼峰式 | Properties 文件
YAML 文件
系统属性 | geektime.springBoot.firstDemo | +| 下划线分割 | Properties 文件
YAML 文件
系统属性 | geektime.spring_boot.first_demo | +| 全⼤大写,下划线分隔 | 环境变量 | GEEKTIME_SPRINGBOOT_FIRSTDEMO | + +### 74 | 理解配置背后的 PropertySource 抽象 + +### 添加 PropertySource + +- `` +- `PropertySourcesPlaceholderConfigurer` +- `PropertyPlaceholderConfigurer` +- `@PropertySource` +- `@PropertySources` + +### Spring Boot 中的 @ConfigurationProperties + +- 可以将属性绑定到结构化对象上 +- 支持 Relaxed Binding +- 支持安全的类型转换 +- `@EnableConfigurationProperties` + +### 定制 PropertySource + +**主要步骤** + +- 实现 `PropertySource` +- 从 `Environment` 取得 `PropertySources` +- 将自己的 `PropertySource` 添加到合适的位置 + +**切入位置** + +- `EnvironmentPostProcessor` +- `BeanFactoryPostProcessor` + +## 第十章:运行中的 Spring Boot (11 讲) + +### 75 | 认识 Spring Boot 的各类 Actuator Endpoint + +### Actuator + +**目的** + +- 监控并管理应用程序 + +**访问方式** + +- HTTP +- JMX + +**依赖** + +- spring-boot-starter-actuator + +### 一些常用 Endpoint + +| ID | 说明 | 默认开启 | 默认 HTTP | 默认 JMX | +| -------------- | ------------------------------------- | -------- | --------- | -------- | +| beans | 显示容器中的 Bean 列表 | Y | N | Y | +| caches | 显示应用中的缓存 | Y | N | Y | +| conditions | 显示配置条件的计算情况 | Y | N | Y | +| configprops | 显示 @ConfigurationProperties 的信息 | Y | N | Y | +| env | 显示 ConfigurableEnvironment 中的属性 | Y | N | Y | +| health | 显示健康检查信息 | Y | Y | Y | +| httptrace | 显示 HTTP Trace 信息 | Y | N | Y | +| info | 显示设置好的应用信息 | Y | Y | Y | +| loggers | 显示并更新日志配置 | Y | N | Y | +| metrics | 显示应用的度量信息 | Y | N | Y | +| mappings | 显示所有的 @RequestMapping 信息 | Y | N | Y | +| scheduledtasks | 显示应用的调度任务信息 | Y | N | Y | +| shutdown | 优雅地关闭应用程序 | N | N | Y | +| threaddump | 执行 Thread Dump | Y | N | Y | +| heapdump | 返回 Heap Dump 文件,格式为 HPROF | Y | N | N/A | +| prometheus | 返回可供 Prometheus 抓取的信息 | Y | N | N/A | + +### 如何访问 Actuator Endpoint + +**HTTP 访问** + +- `/actuator/` + +**端口与路径** + +- `management.server.address=` +- `management.server.port=` +- `management.endpoints.web.base-path=/actuator` +- `management.endpoints.web.path-mapping.=路径` + +**开启 Endpoint** + +- `management.endpoint..enabled=true` +- `management.endpoints.enabled-by-default=false` + +**暴露 Endpoint** + +- `management.endpoints.jmx.exposure.exclude=` +- `management.endpoints.jmx.exposure.include=*` +- `management.endpoints.web.exposure.exclude=` +- `management.endpoints.web.exposure.include=info, health` + +### 76 | 动手定制自己的 Health Indicator + +### Spring Boot 自带的 Health Indicator + +目的 + +- 检查应用程序的运行状态 + +状态 + +- DOWN - 503 +- OUT_OF_SERVICE - 503 +- UP - 200 +- UNKNOWN - 200 + +机制 + +- 通过 `HealthIndicatorRegistry` 收集信息 +- `HealthIndicator` 实现具体检查逻辑 + +配置项 + +- `management.health.defaults.enabled=true|false` +- `management.health..enabled=true` +- `management.endpoint.health.show-details=never|whenauthorized|always` + +**内置 HealthIndicator 清单** + +- `CassandraHealthIndicator` + +- `ElasticsearchHealthIndicator` + +- `MongoHealthIndicator` + +- `SolrHealthIndicator` + +- `CouchbaseHealthIndicator` + +- `InfluxDbHealthIndicator` + +- `Neo4jHealthIndicator` + +- `DiskSpaceHealthIndicator` + +- `JmsHealthIndicator` + +- `RabbitHealthIndicator` + +- `DataSourceHealthIndicator` + +- `MailHealthIndicator` + +- `RedisHealthIndicator` + +### 自定义 Health Indicator + +**方法** + +- 实现 HealthIndicator 接口 +- 根据自定义检查逻辑返回对应 Health 状态 +- Health 中包含状态和详细描述信息 + +### 77 | 通过 Micrometer 获取运行数据 + +### 认识 Micrometer + +**特性** + +- 多维度度量量 +- 支持 Tag +- 预置大量探针 +- 缓存、类加载器器、GC、CPU 利利⽤用率、线程池…… +- 与 Spring 深度整合 + +**支持多种监控系统** + +- Dimensional + + - AppOptics, Atlas, Azure Monitor, Cloudwatch, Datadog, Datadog StatsD, Dynatrace, Elastic, Humio, Influx, KairosDB, New Relic, Prometheus, SignalFx, Sysdig StatsD, Telegraf + StatsD, Wavefront + +- Hierarchical + - Graphite, Ganglia, JMX, Etsy StatsD + +### 一些核心度量指标 + +**核心接口** + +- Meter + +**内置实现** + +- Gauge, TimeGauge +- Timer, LongTaskTimer, FunctionTimer +- Counter, FunctionCounter +- DistributionSummary + +### Micrometer in Spring Boot 2.x + +**一些 URL** + +- `/actuator/metrics` +- `/actuator/prometheus` + +**一些配置项** + +- `management.metrics.export.*` +- `management.metrics.tags.*` +- `management.metrics.enable.*` +- `management.metrics.distribution.*` +- `management.metrics.web.server.auto-time-requests` + +核心度量项 + +- JVM、CPU、文件句柄数、日志、启动时间 + +其他度量项 + +- Spring MVC、Spring WebFlux +- Tomcat、Jersey JAX-RS +- RestTemplate、WebClient +- 缓存、数据源、Hibernate +- Kafka、RabbitMQ + +自定义度量指标 + +- 通过 MeterRegistry 注册 Meter +- 提供 MeterBinder Bean 让 Spring Boot ⾃自动绑定 +- 通过 MeterFilter 进⾏行行定制 + +### 78 | 通过 Spring Boot Admin 了解程序的运行状态 + +### Spring Boot Admin + +**目的** + +- 为 Spring Boot 应用程序提供一套管理界面 + +**主要功能** + +- 集中展示应用程序 Actuator 相关的内容 +- 变更通知 + +### 快速上手 + +**服务端** + +- `de.codecentric:spring-boot-admin-starter-server:2.1.3` +- `@EnableAdminServer` + +**客户端** + +- `de.codecentric:spring-boot-admin-starter-client:2.1.3` +- 配置服务端及 Endpoint +- `spring.boot.admin.client.url=http://localhost:8080` +- `management.endpoints.web.exposure.include=*` + +### 安全控制 + +安全相关依赖 + +- spring-boot-starter-security + +服务端配置 + +- spring.security.user.name +- spring.security.user.password + +### 79 | 如何定制 Web 容器的运行参数 + +### 内嵌 Web 容器 + +可选容器列表 + +- `spring-boot-starter-tomcat` +- `spring-boot-starter-jetty` +- `spring-boot-starter-undertow` +- `spring-boot-starter-reactor-netty` + +### 修改容器器配置 + +**端口** + +- `server.port` +- `server.address` + +**压缩** + +- `server.compression.enabled` +- `server.compression.min-response-size` +- `server.compression.mime-types` + +**Tomcat 特定配置** + +- `server.tomcat.max-connections=10000` +- `server.tomcat.max-http-post-size=2MB` +- `server.tomcat.max-swallow-size=2MB` +- `server.tomcat.max-threads=200` +- `server.tomcat.min-spare-threads=10` + +错误处理 + +- `server.error.path=/error` +- `server.error.include-exception=false` +- `server.error.include-stacktrace=never` +- `server.error.whitelabel.enabled=true` + +其他 + +- `server.use-forward-headers` +- `server.servlet.session.timeout` + +编程方式 + +- `WebServerFactoryCustomizer` +- `TomcatServletWebServerFactory` +- `JettyServletWebServerFactory` +- `UndertowServletWebServerFactory` + +### 80 | 如何配置容器支持 HTTP/2(上) + +### 配置 HTTPS 支持 + +**通过参数进行配置** + +- `server.port=8443` +- `server.ssl.*` + - `server.ssl.key-store` + - server.ssl.key-store-type,JKS 或者 PKCS12 + - `server.ssl.key-store-password=secret` + +### 生成证书文件 + +**命令** + +- keytool -genkey -alias 别名 + - -storetype 仓库类型 -keyalg 算法 -keysize 长度 + - -keystore 文件名 -validity 有效期 + +说明 + +- 仓库类型,JKS、JCEKS、PKCS12 等 +- 算法,RSA、DSA 等 +- 长度,例如 2048 + +### 客户端 HTTPS 支持 + +配置 HttpClient ( >= 4.4 ) + +- `SSLContextBuilder` 构造 `SSLContext` +- `setSSLHostnameVerifier(new NoopHostnameVerifier())` + +配置 `RequestFactory` + +- `HttpComponentsClientHttpRequestFactory` +- `setHttpClient()` + +### 81 | 如何配置容器支持 HTTP/2(下) + +### 配置 HTTP/2 支持 + +前提条件 + +- Java >= JDK 9 +- Tomcat >= 9.0.0 +- Spring Boot 不支持 h2c,需要先配置 SSL + +配置项 + +- server.http2.enabled + +### 客户端 HTTP/2 支持 + +HTTP 库选择 + +- `OkHttp( com.squareup.okhttp3:okhttp:3.14.0 )` +- `OkHttpClient` + +RestTemplate 配置 + +- `OkHttp3ClientHttpRequestFactory` + +### 82 | 如何编写命令行运行的程序 + +### 关闭 Web 容器 + +控制依赖 + +- 不添加 Web 相关依赖 + +配置方式 + +- `spring.main.web-application-type=none` + +编程方式 + +- `SpringApplication` +- `setWebApplicationType()` +- `SpringApplicationBuilder` +- `web()` +- 在调用 `SpringApplication` 的 `run()` 方法前设置 `WebApplicationType` + +### 常用工具类 + +不同的 Runner + +- ApplicationRunner +- 参数是 ApplicationArguments +- CommandLineRunner +- 参数是 String[] + +返回码 + +- ExitCodeGenerator + +### 83 | 了解可执行 Jar 背后的秘密 + +### 认识可执行 Jar + +**其中包含** + +- Jar 描述,`META-INF/MANIFEST.MF` +- Spring Boot Loader,org/springframework/boot/loader +- 项目内容,BOOT-INF/classes +- 项目依赖,BOOT-INF/lib + +**其中不包含** + +- JDK / JRE + +### 如何找到程序的入口 + +**Jar 的启动类** + +- `MANIFEST.MF` +- `Main-Class: org.springframework.boot.loader.JarLauncher` + +**项目的主类** + +- `@SpringApplication` +- `MANIFEST.MF` +- `Start-Class: xxx.yyy.zzz` + +### 84 | 如何将 Spring Boot 应用打包成 Docker 镜像文件 + +### 什么是 Docker 镜像 + +- 镜像是静态的只读模板 +- 镜像中包含构建 Docker 容器器的指令 +- 镜像是分层的 +- 通过 Dockerfile 来创建镜像 + +### Dockerfile + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230806142644.png) + +### 通过 Maven 构建 Docker 镜像 + +准备工作 + +- 提供一个 Dockerfile +- 配置 dockerfile-maven-plugin 插件 + +执行构建 + +- `mvn package` +- `mvn dockerfile:build` + +检查结果 + +- `docker images` + +### 85 | SpringBucks 实战项目进度小结 + +略 + +## 第十一章:Spring Cloud 及 Cloud Native 概述 (5 讲) + +### 86 | 简单理解微服务 + +微服务就是一些协同工作的小而自治的服务。 + +微服务的优点 + +- 易于部署 +- 与组织结构对齐 +- 可组合性 +- 可替代性 + +微服务的代价 + +- 架构复杂 +- 运维复杂 + +### 87 | 如何理解云原生(Cloud Native) + +云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。 + +云原生应用要求 + +- DevOps +- 持续交付 +- 微服务 +- 容器 + +Cloud Native Computing Foundation,缩写 CNCF + +### 88 | 12-Factor App(上) + +### 89 | 12-Factor App(下) + +12-Factor 为构建 SaaS 应用提供了方法论。 + +参考资料:https://12factor.net/zh_cn/ + +- 基准代码 - 一份基准代码,多份部署。解决方案:git + +- 依赖 - 显式声明依赖关系。解决方案:maven、gradle + +- 配置 - 在环境中存储配置。解决方案:apollo + +- 后端服务 - 把后端服务当作附加资源 + +- 构建,发布,运行 - 严格分离构建和运行。解决方案:CI/CD(如:jenkins、sonar 等) + +- 进程 - 以一个或多个无状态进程运行应用 + +- 端口绑定 - 通过端口绑定提供服务 + +- 并发 - 通过进程模型进行扩展 + +- 易处理 - 快速启动和优雅终止可最大化健壮性 + +- 开发环境与线上环境等价 - 尽可能的保持开发,预发布,线上环境相同 + +- 日志 - 把日志当作事件流 + +- 管理进程 - 后台管理任务当作一次性进程运行 + +### 90 | 认识 Spring Cloud 的组成部分 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230729155944.svg) + +Spring Cloud 的主要功能 + +- 服务发现 +- 服务熔断 +- 配置服务 +- 服务安全 +- 服务网关 +- 分布式消息 +- 分布式跟踪 +- 各种云平台支持 + +## 第十二章:服务注册与发现 (9 讲) + +### 91 | 使用 Eureka 作为服务注册中心 + +- **SpringCloud 启动包** + - 服务端 - `spring-cloud-starter-netflix-eureka-server` + - 客户端 - `spring-cloud-starter-netflix-eureka-client` +- **注解** + - 服务端启动注解 - `@EnableEurekaServer` + - 客户端启动注解 + - 通用注解 - `@EnableDiscoveryClient` + - Eureka 特定注解 - `@EnableEurekaClient` +- **要点** + - Eureka 默认端口 8761 +- **配置** + - `eureka.client.serviceUrl.defaultZone` - 注册地址,如 http://localhost:10001/eureka/ + - `eureka.client.register-with-eureka` - 是否将自己注册到 Eureka Server,默认为 true + - `eureka.client.fetch-registry` - 是否从 Eureka Server 获取注册信息,默认为 true + +### 92 | 使用 Spring Cloud Loadbalancer 访问服务 + +- 如何获得服务地址 + - `org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient` + - `org.springframework.cloud.client.discovery.DiscoveryClient` - 通用接口,推荐方式 +- 负载均衡客户端 + - `@LoadBalanced` + - 实际是通过 `ClientHttpRequestInterceptor` 实现的 + - `LoadBalancerInterceptor` + - `LoadBalancerClient` + - `RibbonLoadBalancerClient` + +### 93 | 使用 Feign 访问服务 + +声明式 REST Web 服务客户端 + +- **SpringCloud 启动包** + - spring-cloud-starter-openfeign +- **注解** + - 启动注解 - `@EnableFeignClients` + - 定义接口注解 - `@FeignClient` +- **配置** - `org.springframework.cloud.openfeign.FeignClientsConfiguration` + +### 94 | 深入理解服务发现背后的 DiscoveryClient + +- **服务端抽象接口** - `org.springframework.cloud.client.serviceregistry.ServiceRegistry` + - `EurekaServiceRegistry` + - `EurekaRegistration` + - `EurekaAutoServiceRegistration` + - `EurekaClientAutoConfiguration` +- **客户端抽象接口** - `org.springframework.cloud.client.discovery.DiscoveryClient` + - `@EnableDiscoveryClient` +- **负载均衡抽象接口** - `org.springframework.cloud.client.loadbalancer.LoadBalancerClient` + +### 95 | 使用 Zookeeper 作为服务注册中心 + +- **SpringCloud 启动包** + - **spring-cloud-starter-zookeeper-discovery** +- **配置** + - `ZookeeperAutoConfiguration` + - `ZookeeperDiscoveryAutoConfiguration` + +### 96 | 使用 Consul 作为服务注册中心 + +- **SpringCloud 启动包** + - **spring-cloud-starter-consul-discovery** +- **配置** + - `ConsulAutoConfiguration` + +### 97 | 使用 Nacos 作为服务注册中心 + +- **SpringCloud 启动包** + - **spring-cloud-starter-alibaba-nacos-discovery** +- **配置** + - `NacosDiscoveryAutoConfiguration` + +### 98 | 如何定制自己的 DiscoveryClient + +**`DiscoveryClient`** + +- `EurekaDiscoveryClient` +- `ZooKeeperDiscoveryClient` +- `ConsulDiscoveryClient` +- `NacosDiscoveryClient` + +**`LoadBalancerClient`** + +- `RibbonLoadBalancerClient` + +**自定义 DiscoveryClient 步骤** + +- 返回该 DiscoveryClient 能提供的服务名列表 +- 返回指定服务对应的 ServiceInstance 列表 +- 返回 DiscoveryClient 的顺序 +- 返回 HealthIndicator 里显示的描述 + +自定义 RibbonClient 支持 + +- 实现 `ServerList` +- Ribbon 提供了 AbstractServerList +- 提供一个配置类,声明 ServerList Bean 实例 + +### 99 | SpringBucks 实战项目进度小结 + +略 + +## 第十三章:服务熔断 (7 讲) + +### 100 | 使用 Hystrix 实现服务熔断(上) + +断路器模式 + +在断路器对象中封装受保护的方法调用 + +该对象监控调用和断路情况 + +调用失败触发阈值后,后序调用直接由断路器返回错误,不再执行实际调用 + +### 101 | 使用 Hystrix 实现服务熔断(下) + +- **Hystrix 应用** + + - 注解 + - @HystrixCommand + - fallbackMethod + - commandProperties + - @HystrixProperty + +- **SpringCloud 启动包** + - **spring-cloud-starter-netflix-hystrix** +- **注解** + + - `@EnableCircuitBreaker` - 断路器开启注解 + +- **Feign 支持** + - `feign.hystrix.enabled=true` + - `@FeignClient` 的 `fallback` / `fallbackFactory` +- **配置** + - `HystrixCircuitBreakerAutoConfiguration` + +### 102 | 如何观察服务熔断 + +Spring Cloud 对于熔断的监控支持 + +- Hystrix Metrics Stream + - spring-boot-starter-actuator + - `/actuator/hystrix.stream` +- Hystrix Dashboard + - spring-cloud-starter-netflix-hystrix-dashboard + - `@EnableHystrixDashboard` + - `/hystirx` + +**聚合集群熔断信息** + +- **SpringCloud 启动包** - spring-cloud-starter-netflix-turbines +- **注解** - `@EnableTurbine` +- `/turbine/stream?cluster=集群名` + +### 103 | 使用 Resilience4j 实现服务熔断 + +Hystrix 官方已经停止维护,因此建议选择其他产品来替代。例如:[Resilience4J](https://github.com/resilience4j/resilience4j) + +- Resilience4J 实现 + - 基于 `ConcurrentHashMap` 的内存断路器 + - `CircuitBreakerRegistry` + - `CircuitBreakerConfig` +- Resilience4J 依赖 + + - resilience4j-spring-boot2 + +- 注解 + - @CircuitBreaker +- 配置 + - CircuitBreakerProperties + +### 104 | 使用 Resilience4j 实现服务限流(上) + +Bulkhead + +- 目的 + - 防止下游依赖被并发请求冲击 + - 防止发生雪崩 +- 用法 + - BulkheadRegistry / BulkheadConfig + - @Bulkhead(name = "xxx") + +### 105 | 使用 Resilience4j 实现服务限流(下) + +RateLimit + +- 目的 + - 限制特定时间内的执行次数 +- 用法 + - `RateLimiterRegistry` / `RateLimiterConfig` + - `@RateLimiter` +- 配置 +- RateLimiterPropertis + +### 106 | SpringBucks 实战项目进度小结 + +略 + +## 第十四章:服务配置 (7 讲) + +### 107 | 基于 Git 的配置中心(上) + +目的 + +提供针对外置配置的 HTTP API + +- **SpringCloud 启动包** - spring-cloud-config-server +- **注解** - `@EnableConfigServer` + +### 108 | 基于 Git 的配置中心(下) + +### 109 | 基于 Zookeeper 的配置中心 + +### 110 | 深入理解 Spring Cloud 的配置抽象 + +实现 + +- 类似于 Spring 的 Environment 和 PropertySource +- 在上下文中增加 Spring Cloud Config 的 PropertySource + +PropertySource 子类 + +- `ZooKeeperPropertySource` +- `ConsulPropertySource` +- `ConsulFilePropertySource` + +PropertySourceLocator + +EnvironmentRepositry + +配置刷新 + +- /actuator/refresh +- Spring Cloud Bus - RegfreshRemoteApplicationEvent + +ZooKeeperConfigBootstrapConfiguration + +ZooKeeperConfigAutoConfiguration + +### 111 | 基于 Consul 的配置中心 + +**SpringCloud 启动包** - spring-cloud-starter-consual-config + +**配置文件** - bootstrap.propertis | yml + +### 112 | 基于 Nacos 的配置中心 + +**SpringCloud 启动包** - spring-cloud-starter-alibaba-nacos-config + +**配置文件** - bootstrap.propertis | yml + +### 113 | SpringBucks 实战项目进度小结 + +略 + +## 第十五章:Spring Cloud Stream (4 讲) + +### 114 | 认识 Spring Cloud Stream + +Spring Cloud Stream 是一款用于构建消息驱动的微服务应用程序的轻量级框架。 + +特性 + +- 声明式编程模型 +- 引入多种概念抽象:发布订阅、消费组、分区 +- 支持多种消息中间件:RabbitMQ、Kafka + +概念 + +- Binding + - 生产者、消费者与 MQ 之间的桥梁 + - @EnableBinding + - @Input /SubscribableChannel + - @Output / MessageChannel +- 消费组 +- 分区 + +生产消息 + +- 使用 MessageChannel 的 send() +- @SendTo + +消费消息 + +- @StreamListener +- @Payload / @Headers / @Header + +### 115 | 通过 Spring Cloud Stream 访问 RabbitMQ + +**SpringCloud 启动包** - spring-cloud-starter-stream-rabbit + +**SpringBoot 启动包** - spring-boot-starter-amqp + +**配置** + +`org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration` + +### 116 | 通过 Spring Cloud Stream 访问 Kafka + +**SpringCloud 启动包** - spring-cloud-starter-stream-kafka + +**配置** - `org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration` + +Spring 定时任务 + +- TaskScheduler / Trigger / TriggerContext +- 配置定时任务 + - `@EnableScheduling` + - `` + - `@Scheduled` + +Spring 事件机制 + +- `ApplicationEvent` +- 发送事件 + - `ApplicationEventPublisher` + - `ApplicationEventPublisherAware` +- 监听事件 + - `ApplicationListener` + - `@EventListener` + +### 117 | SpringBucks 实战项目进度小结 + +略 + +## 第十六章:服务链路追踪 (6 讲) + +### 118 | 通过 Dapper 理解链路治理 + +略 + +### 119 | 使用 Spring Cloud Sleuth 实现链路追踪 + +**SpringCloud 启动包** - spring-cloud-starter-sleuth、spring-cloud-starter-zipkin + +### 120 | 如何追踪消息链路 + +略 + +### 121 | 除了链路还要治理什么 + +略 + +### 122 | SpringBucks 实战项目进度小结 + +略 + +## 参考资料 + +- [玩转 Spring 全家桶](https://time.geekbang.org/course/intro/156) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/01.\344\273\2160\345\274\200\345\247\213\345\255\246\346\236\266\346\236\204\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/01.\344\273\2160\345\274\200\345\247\213\345\255\246\346\236\266\346\236\204\347\254\224\350\256\260.md" index 7ddaa45be0..3af3aba3cf 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/01.\344\273\2160\345\274\200\345\247\213\345\255\246\346\236\266\346\236\204\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/01.\344\273\2160\345\274\200\345\247\213\345\255\246\346\236\266\346\236\204\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《从0开始学架构》笔记 date: 2022-04-15 09:38:33 +order: 01 categories: - 笔记 - 设计 @@ -40,7 +41,7 @@ permalink: /pages/531ef7/ ## 架构设计原则案例 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220415104328.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220415104328.png) ## 参考资料 diff --git "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/02.\346\236\266\346\236\204\345\256\236\346\210\230\346\241\210\344\276\213\350\247\243\346\236\220\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/02.\346\236\266\346\236\204\345\256\236\346\210\230\346\241\210\344\276\213\350\247\243\346\236\220\347\254\224\350\256\260.md" index e59a8ad80c..659613ed10 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/02.\346\236\266\346\236\204\345\256\236\346\210\230\346\241\210\344\276\213\350\247\243\346\236\220\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/02.\346\236\266\346\236\204\345\256\236\346\210\230\346\241\210\344\276\213\350\247\243\346\236\220\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《架构实战案例解析》笔记 date: 2021-08-26 23:32:00 +order: 02 categories: - 笔记 - 设计 @@ -60,7 +61,7 @@ permalink: /pages/c75707/ **业务的主题是变化和创新,系统的主题是稳定和可靠**。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220630230555.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220630230555.png) ### 架构目标之业务的可复用 @@ -72,7 +73,7 @@ permalink: /pages/c75707/ 最后,**实现模块的高复用,还需要做好业务的层次划分**。我们知道,越是底层的业务,它就相对更固定。举个例子,同样是订单业务域,对于底层订单的增删改查功能,不同类型的订单都是一样的,但对于上层的订单生命周期管理,外卖订单和堂食订单可能就不一样。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220630231612.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220630231612.png) ## 可扩展架构:如何打造一个善变的柔性系统? @@ -111,7 +112,7 @@ permalink: /pages/c75707/ 电商平台架构发展的大致过程: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701065653.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701065653.png) ### 单体架构 @@ -119,7 +120,7 @@ permalink: /pages/c75707/ 单体应用内部一般采用分层结构,从上到下,一般分为表示层、业务层、数据访问层、DB 层。表示层负责用户体验,业务层负责业务逻辑,数据访问层负责 DB 的数据存取。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701065747.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701065747.png) ### 分布式架构 @@ -127,7 +128,7 @@ permalink: /pages/c75707/ 分布式架构包括了多个应用,每个应用分别负责不同的业务线,当一个应用需要另一个应用的功能时,会通过 API 接口进行调用。在分布式架构中,API 接口属于应用的一部分,它和表示层共享底层的业务逻辑。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701070033.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701070033.png) 分布式架构适用于业务相关性低、耦合少的业务系统。 @@ -135,7 +136,7 @@ permalink: /pages/c75707/ SOA 架构(Service Oriented Architecture)是一种面向服务的架构,它的发展经历了两个阶段:传统的 SOA 架构,它解决的是企业内部大量异构系统集成的问题;新的 SOA 架构,它解决的是系统重复建设的问题。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701070410.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701070410.png) 在 SOA 架构中,每个服务都对应一个现有的系统,所有这些服务都部署在一个中心化的平台上,我们称之为企业服务总线 ESB(Enterprise Service Bus),ESB 负责管理所有调用过程的技术复杂性,包括服务的注册和路由、各种通信协议的支持等等。 @@ -145,13 +146,13 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, 一方面,微服务强调所谓的哑管道,即客户端可以通过 HTTP 等简单的技术手段,访问微服务,避免重的通信协议和数据编码支持。另一方面,微服务强调智能终端,所有的业务逻辑包含在微服务内部,不需要额外的中间层提供业务规则处理。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701070749.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701070749.png) ## 可扩展架构案例(二):App 服务端架构是如何升级的? ### V1.0 架构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701071239.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701071239.png) 问题: @@ -161,7 +162,7 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, ### V2.0 架构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701071257.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701071257.png) 问题: @@ -173,7 +174,7 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, 首先,我们对每个业务线的服务端进行拆分,让 App 接口和 PC 端接口各自在物理上独立,但它们共享核心的业务逻辑。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701071719.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701071719.png) #### 移动网关的内部实现 @@ -189,7 +190,7 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, ## 可扩展架构案例(三):你真的需要一个中台吗? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701072503.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701072503.png) 前台:面向 C 端的应用。前台对外 @@ -201,7 +202,7 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, 第一种是独立地建设新业务线,这样,各个业务线并列,系统整体上是一个“川”字型的结构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701152539.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701152539.png) 第二种做法是,把各业务线中相同的核心逻辑抽取出来,通过抽象设计,实现通用化,共同服务于所有业务线的需求,系统结构整体上是一个“山”字型。这样,我们就能一处建设,多处复用,一处修改,多处变化,从而实现最大程度的复用。 @@ -218,11 +219,11 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, 松散的微服务 -> 共享服务体系 -> 中台 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701153820.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701153820.png) 传统企业中台架构设计 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701153933.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701153933.png) 中台代表了企业核心的业务能力,它自成体系,能够为 C 端的互联网场景提供通用的能力,并通过各种插件和后台打通。 @@ -233,7 +234,7 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, 从复用的程度来看,从高到低,我们可以依次划分为产品复用 > 业务流程复用 > 业务实体复用 > 组件复用 > 代码复用。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701154211.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701154211.png) 技术复用:代码级复用和技术组件复用都属于工具层面,它们的好处是在很多地方都可以用,但和业务场景隔得有点远,不直接对应业务功能,因此复用的价值相对比较低。 @@ -247,9 +248,9 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, 对于落地一个共享服务来说,服务边界的划分和功能的抽象设计是核心。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701160043.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701160043.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220701160116.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220701160116.png) ## 可复用架构案例(二):如何对现有系统做微服务改造? @@ -264,15 +265,15 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, 1. 业务上有什么重大变化,导致当前系统的弊端已经很明显,不能适应业务发展了呢? 2. 架构改造时,如何在业务、系统、资源三者之间做好平衡,对系统进行分步式的改造呢? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220702222041.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220702222041.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220702222341.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220702222341.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220702222438.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220702222438.png) ## 技术架构:作为开发,你真的了解系统吗? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703085109.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703085109.png) 技术架构的职责,首先是负责系统所有组件的技术选型,然后确保这些组件可以正常运行。 @@ -289,7 +290,7 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, ## 高可用架构:如何让你的系统不掉链子? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703092202.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703092202.png) 故障分类 @@ -303,23 +304,23 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, ## 高可用架构案例(一):如何实现 O2O 平台日订单 500 万? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703092811.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703092811.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703092957.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703092957.png) ## 高可用架构案例(二):如何第一时间知道系统哪里有问题? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703093328.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703093328.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703093652.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703093652.png) ## 高可用架构案例(三):如何打造一体化的监控系统? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703103558.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703103558.png) ## 高性能和可伸缩架构:业务增长,能不能加台机器就搞定? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703103855.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703103855.png) - 加快单个请求处理 - 优化处理路径上每个节点的处理速度 @@ -338,61 +339,59 @@ SOA 架构(Service Oriented Architecture)是一种面向服务的架构, ## 高性能架构案例:如何设计一个秒杀系统? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703105340.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703105340.png) ## 可伸缩架构案例:数据太多,如何无限扩展你的数据库? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703110828.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703110828.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703110925.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703110925.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703111011.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703111011.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703135733.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703135733.png) ## 案例:电商平台技术架构是如何演变的? 单体架构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703135849.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703135849.png) SOA 架构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703135906.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703135906.png) 微服务架构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703135939.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703135939.png) 垂直拆分(分库) -![image-20220703140016738](C:\Users\zp\AppData\Roaming\Typora\typora-user-images\image-20220703140016738.png) - 水平拆分 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703140033.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703140033.png) 多机房部署 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703140123.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703140123.png) 服务调用本地化 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703140249.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703140249.png) 依赖分级管理 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703140310.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703140310.png) 多机房独立部署 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703140404.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703140404.png) ## 从务实的角度,给你架构设计的重点知识和学习路径 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703141209.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703141209.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703141328.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703141328.png) ## 参考资料 diff --git "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/03.\345\267\246\350\200\263\345\220\254\351\243\216\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/03.\345\267\246\350\200\263\345\220\254\351\243\216\347\254\224\350\256\260.md" index a1f71308a1..14e1c40dbd 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/03.\345\267\246\350\200\263\345\220\254\351\243\216\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/03.\345\267\246\350\200\263\345\220\254\351\243\216\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《左耳听风》笔记 date: 2021-08-15 15:27:00 +order: 03 categories: - 笔记 - 设计 diff --git "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/10.\344\273\2160\345\274\200\345\247\213\345\255\246\345\276\256\346\234\215\345\212\241.md" "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/10.\344\273\2160\345\274\200\345\247\213\345\255\246\345\276\256\346\234\215\345\212\241.md" index 1c7aedaff1..27295a3964 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/10.\344\273\2160\345\274\200\345\247\213\345\255\246\345\276\256\346\234\215\345\212\241.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/10.\344\273\2160\345\274\200\345\247\213\345\255\246\345\276\256\346\234\215\345\212\241.md" @@ -1,6 +1,7 @@ --- title: 《从 0 开始学微服务》笔记 date: 2021-08-15 15:27:00 +order: 10 categories: - 笔记 - 设计 diff --git "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/11.\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/11.\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" index 6163e4b5e8..75b30f90f7 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/11.\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/11.\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《微服务架构核心 20 讲》笔记 date: 2022-06-26 18:09:46 +order: 11 categories: - 笔记 - 设计 @@ -51,7 +52,7 @@ permalink: /pages/b4661f/ **康威法则**:设计系统的架构受制于产生这些设计的组织的沟通结构。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627061813.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627061813.png) 康威的原文中提出的各定律 @@ -76,13 +77,13 @@ permalink: /pages/b4661f/ ## 什么样组织架构更适合微服务 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627063405.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627063405.png) - 左边是比较传统的组织架构。产品从左到右流程走,可能出现的问题,反馈比较慢,对业务支持比较慢。沟通成本比较大。 - 右边是比较合适微服务的组织架构, 每一个团队(基于微服务的跨职能的团队),有开发,有产品,有测试,团队都支持自己的微服务。交付的产口是平台,对外提供 API 接口支持多样的业务。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627064331.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627064331.png) DevOps 理念:谁开发的,谁构建,谁支持。 @@ -104,7 +105,7 @@ PaaS 和 核心业务层是和微服务相关的。这一些基本都可以用 大致的服务分层图: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627064948.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627064948.png) SOA(Service-Oriented Architecture)或微服务大致可分为 @@ -113,7 +114,7 @@ SOA(Service-Oriented Architecture)或微服务大致可分为 ## 微服务总体技术架构体系是怎么设计的 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627065252.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627065252.png) - 接入层:接入外部流量,内部做负载均衡 - 网关层:反向路由,限流,安全,跨横切面的功能。 @@ -130,21 +131,21 @@ SOA(Service-Oriented Architecture)或微服务大致可分为 (1)通过 DNS 访问 LB(负载均衡),LB 分发 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627070054.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627070054.png) (2)消费者内置 LB, 生产者将自身信息注册到注册中心上,并通过发送定时心跳来确认自身服务可用。消费者定期从注册中心拉取生产者信息 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627070105.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627070105.png) (3)结全前面两种方式, 在 Consumer 的主机上也布置一个 LB。 LB 会定期同步注册中心的信息。 运维成本比较高一点。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220627070117.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220627070117.png) ## 微服务 API 服务网关(一)原理 网关用于屏蔽服务内部的逻辑,希望外部访问看到是统一的接口。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070638.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070638.png) 网关主要的功能: @@ -155,7 +156,7 @@ SOA(Service-Oriented Architecture)或微服务大致可分为 一般不要把过多的业务逻辑写在网关当中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070651.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070651.png) ## 服务 API 服务网关(二)开源网关 Zuul @@ -165,11 +166,11 @@ Servlet 和 Filter Runner 过滤器:前置路由过滤器, 路由过滤器 各个 Filter 共享数据通过 Request Context 来实现。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070702.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070702.png) 过滤链的流程: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070712.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070712.png) ## 跟 Netflix 学习微服务路由发现体系 @@ -178,7 +179,7 @@ netflix 有两个比较重要的支撑服务 - 服务注册中心 Eureka - 网关 zuul -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070723.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070723.png) ## 集中式配置中心的作用和原理是什么 @@ -196,11 +197,11 @@ netflix 有两个比较重要的支撑服务 Svr 更新配置有两种方式:推和拉。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070748.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070748.png) 携程的 Apollo 配置中心: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070805.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070805.png) github : https://github.com/ctripcorp/apollo @@ -210,7 +211,7 @@ RPC:远程过程调用 REST:Restful -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070816.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070816.png) ## 微服务框架需要考虑哪些治理环节 @@ -224,7 +225,7 @@ REST:Restful 契约生成代码: 定义结构体可自动生成 json 格式, vscode 有插件。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070827.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070827.png) 阿里巴巴微服务治理生态:Dubbo http://dubbo.apache.org/en-us/ @@ -246,7 +247,7 @@ REST:Restful - 登录注册 - 端用户体验监控 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070913.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070913.png) - 日志监控:Elasticsearch - metrics 监控 @@ -256,7 +257,7 @@ REST:Restful 比较典型的监控架构,大部分公司的流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628070950.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628070950.png) 数据量比较大一般用 Kafka 作为缓冲队列。 @@ -270,17 +271,17 @@ ELK:ELK 是 Elasticsearch、Logstash、Kibana 三大开源框架首字母大 通过 Span 来跟踪, RootSpan ChildSpan 跨进程时 会有 Trace di + parant span id -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628071003.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628071003.png) 三个主流调用链监控系统的比较: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628071013.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628071013.png) ## 微服务的容错限流是如何工作的 Netfiix Hystrix 具有熔断、隔离、限流、降级的功能 。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628071025.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628071025.png) 说明: @@ -297,18 +298,18 @@ docker 容器治理就是解决:环境不一致的问题。把依赖的所有 UAT 环境: User Acceptance Test (用户验收测试) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628071059.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628071059.png) 发布模式: 蓝绿布置,灰度发布(金丝雀发布)。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628071111.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628071111.png) ## 容器集群调度和基于容器的发布体系 资源调度框架 Mesos 架构 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628071127.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628071127.png) 基于容器的云发布体系 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220628071152.png) \ No newline at end of file +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220628071152.png) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/21.\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\24140\351\227\256\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/21.\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\24140\351\227\256\347\254\224\350\256\260.md" index 52b22281cd..37da418516 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/21.\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\24140\351\227\256\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/03.\350\256\276\350\256\241/21.\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\24140\351\227\256\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《高并发系统设计 40 问》笔记 date: 2021-08-05 23:42:00 +order: 21 categories: - 笔记 - 设计 @@ -145,15 +146,15 @@ Cache Aside 存在的最大的问题是当写入比较频繁时,缓存中的 #### Read/Write Through(读穿 / 写穿)策略 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210808210155.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210808210155.jpg) ### Write Back(写回)策略 核心思想是在写入数据时只写入缓存,并且把缓存块儿标记为“脏”的。而脏块儿只有被再次使用时才会将其中的数据写入到后端存储中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210808210420.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210808210420.jpg) -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210808210511.jpg) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210808210511.jpg) 这种策略不能被应用到我们常用的数据库和缓存的场景中,它是计算机体系结构中的设计,比如我们在向磁盘中写数据时采用的就是这种策略。 diff --git "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/01.\345\220\216\347\253\257\345\255\230\345\202\250\345\256\236\346\210\230\350\257\276\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/01.\345\220\216\347\253\257\345\255\230\345\202\250\345\256\236\346\210\230\350\257\276\347\254\224\350\256\260.md" index ed54707e19..11c9b1f12e 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/01.\345\220\216\347\253\257\345\255\230\345\202\250\345\256\236\346\210\230\350\257\276\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/01.\345\220\216\347\253\257\345\255\230\345\202\250\345\256\236\346\210\230\350\257\276\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《后端存储实战课》笔记 date: 2022-04-08 17:00:00 +order: 01 categories: - 笔记 - 数据库 @@ -14,23 +15,23 @@ permalink: /pages/a16273/ ## 课前加餐丨电商系统是如何设计的? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220407152237.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220407152237.png) ## 创建和更新订单时,如何保证数据准确无误? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220407162459.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220407162459.png) ## 流量大、数据多的商品详情页系统该如何设计? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220407164745.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220407164745.png) ## 复杂而又重要的购物车系统,应该如何设计? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220408142059.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220408142059.png) ## 事务:账户余额总是对不上账,怎么办? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220408152524.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220408152524.png) ## 分布式事务:如何保证多个系统间的数据是一致的? @@ -78,23 +79,19 @@ Mysql 复制(略) 如果出现慢 SQL,需要改造索引时,可以通过执行计划进行分析。 -> 个人过往总结:[Mysql 性能优化](https://dunwu.github.io/db-tutorial/sql/mysql/mysql-optimization.html) - ## 走进黑盒:SQL 是如何在数据库中执行的? -> 个人过往总结:[Mysql 工作流](https://github.com/dunwu/db-tutorial/blob/master/docs/sql/mysql/mysql-workflow.md) - ## MySQL 如何应对高并发(一):使用缓存保护 MySQL -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413101029.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413101029.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413101039.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413101039.png) ## MySQL 如何应对高并发(二):读写分离 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413160150.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413160150.png) -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/mysql/master-slave-proxy.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/mysql/master-slave-proxy.png) ## MySQL 主从数据库同步是如何实现的? @@ -108,13 +105,13 @@ Mysql 复制(略) 分库分表 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220413174922.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220413174922.png) ## 用 Redis 构建缓存集群的最佳实践有哪些 Redis 3.0 后,官方提供 Redis Cluster 来解决数据量大、高可用和高并发问题。 -> 相关文章:[Redis 集群](https://dunwu.github.io/blog/%E6%95%B0%E6%8D%AE%E5%BA%93/05.KV%E6%95%B0%E6%8D%AE%E5%BA%93/01.Redis/07.Redis%E9%9B%86%E7%BE%A4/) +> 相关文章:[Redis 集群](https://dunwu.github.io/waterdrop/%E6%95%B0%E6%8D%AE%E5%BA%93/05.KV%E6%95%B0%E6%8D%AE%E5%BA%93/01.Redis/07.Redis%E9%9B%86%E7%BE%A4/) ## 大厂都是怎么做 MySQL to Redis 同步的? diff --git "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/02.SQL\345\277\205\347\237\245\345\277\205\344\274\232.md" "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/02.SQL\345\277\205\347\237\245\345\277\205\344\274\232.md" index acc6328578..78db02ba4d 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/02.SQL\345\277\205\347\237\245\345\277\205\344\274\232.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/02.SQL\345\277\205\347\237\245\345\277\205\344\274\232.md" @@ -1,20 +1,21 @@ --- title: 《SQL 必知必会》笔记 date: 2022-07-16 10:46:05 +order: 02 categories: - 笔记 - 数据库 tags: - - 架构 - 数据库 + - 关系型数据库 permalink: /pages/34699b/ --- # 《SQL 必知必会》笔记 -## SQL 语法基础 +## 第一章:SQL 语法基础篇 -### SQL 语言划分 +### 01 丨了解 SQL:一门半衰期很长的语言 SQL 语言按照功能划分成以下的 4 个部分: @@ -23,7 +24,7 @@ SQL 语言按照功能划分成以下的 4 个部分: - **DCL**,英文叫做 Data Control Language,数据控制语言,我们用它来定义访问权限和安全级别。 - **DQL**,英文叫做 Data Query Language,数据查询语言,我们用它查询想要的记录,它是 SQL 语言的重中之重。在实际的业务中,我们绝大多数情况下都是在和查询打交道,因此学会编写正确且高效的查询语句,是学习的重点。 -### DB、DBS 和 DBMS 的区别 +### 02 丨 DBMS 的前世今生 DB、DBS 和 DBMS 的区别: @@ -39,9 +40,11 @@ NoSql 不同时期的释义 - 2005:NoSQL = Not only SQL - 2013:NoSQL = No, SQL! -### Oracle 中的 SQL 是如何执行的 +### 03 丨学会用数据库的方式思考 SQL 是如何执行的 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220716105947.png) +#### Oracle 中的 SQL 是如何执行的 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220716105947.png) 1. **语法检查**:检查 SQL 拼写是否正确,如果不正确,Oracle 会报语法错误。 2. **语义检查**:检查 SQL 中的访问对象是否存在。比如我们在写 SELECT 语句的时候,列名写错了,系统就会提示错误。语法检查和语义检查的作用是保证 SQL 语句没有错误。 @@ -56,11 +59,11 @@ NoSql 不同时期的释义 而数据字典缓冲区存储的是 Oracle 中的对象定义,比如表、视图、索引等对象。当对 SQL 语句进行解析的时候,如果需要相关的数据,会从数据字典缓冲区中提取。 -### MySQL 中的 SQL 是如何执行的 +#### MySQL 中的 SQL 是如何执行的 MySQL 是典型的 C/S 架构,即 Client/Server 架构,服务器端程序使用的 mysqld。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220716110905.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220716110905.png) Mysql 可分为三层: @@ -68,7 +71,7 @@ Mysql 可分为三层: 2. **SQL 层**:对 SQL 语句进行查询处理; 3. **存储引擎层**:与数据库文件打交道,负责数据的存储和读取。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220716111103.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220716111103.png) SQL 层的结构 @@ -85,15 +88,26 @@ SQL 层的结构 4. NDB 存储引擎:也叫做 NDB Cluster 存储引擎,主要用于 MySQL Cluster 分布式集群环境,类似于 Oracle 的 RAC 集群。 5. Archive 存储引擎:它有很好的压缩机制,用于文件归档,在请求写入时会进行压缩,所以也经常用来做仓库。 -### 检索数据 +### 04 丨使用 DDL 创建数据库&数据表时需要注意什么? + +DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。 + +设计数据表的原则 + +- **数据表的个数越少越好** - RDBMS 的核心在于对实体和联系的定义,也就是 E-R 图(Entity Relationship Diagram),数据表越少,证明实体和联系设计得越简洁,既方便理解又方便操作。 +- **数据表中的字段个数越少越好** - 字段个数越多,数据冗余的可能性越大。设置字段个数少的前提是各个字段相互独立,而不是某个字段的取值可以由其他字段计算出来。当然字段个数少是相对的,我们通常会在数据冗余和检索效率中进行平衡。 +- **数据表中联合主键的字段个数越少越好** - 设置主键是为了确定唯一性,当一个字段无法确定唯一性的时候,就需要采用联合主键的方式(也就是用多个字段来定义一个主键)。联合主键中的字段越多,占用的索引空间越大,不仅会加大理解难度,还会增加运行时间和索引空间,因此联合主键的字段个数越少越好。 +- **使用主键和外键越多越好** - 数据库的设计实际上就是定义各种表,以及各种字段之间的关系。这些关系越多,证明这些实体之间的冗余度越低,利用度越高。这样做的好处在于不仅保证了数据表之间的独立性,还能提升相互之间的关联使用率。——不同意 + +### 05 丨检索数据:你还在 SELECT 么? SELECT 的作用是从一个表或多个表中检索出想要的数据行。 -> - `SELECT` 语句用于从数据库中查询数据。 -> - `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 -> - `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 -> - `ASC` :升序(默认) -> - `DESC` :降序 +- `SELECT` 语句用于从数据库中查询数据。 +- `DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。 +- `LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 + - `ASC` :升序(默认) + - `DESC` :降序 查询单列 @@ -129,13 +143,157 @@ SELECT * FROM world.country LIMIT 0, 5; SELECT * FROM world.country LIMIT 2, 3; ``` -## SQL 性能优化 +### 06 丨数据过滤:SQL 数据过滤都有哪些方法? + +#### 比较操作符 + +| 运算符 | 描述 | +| ------ | ------------------------------------------------------ | +| `=` | 等于 | +| `<>` | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != | +| `>` | 大于 | +| `<` | 小于 | +| `>=` | 大于等于 | +| `<=` | 小于等于 | + +#### 范围操作符 + +| 运算符 | 描述 | +| --------- | -------------------------- | +| `BETWEEN` | 在某个范围内 | +| `IN` | 指定针对某个列的多个可能值 | + +#### 逻辑操作符 + +| 运算符 | 描述 | +| ------ | ---------- | +| `AND` | 并且(与) | +| `OR` | 或者(或) | +| `NOT` | 否定(非) | + +#### 通配符 + +| 运算符 | 描述 | +| ------ | -------------------------- | +| `LIKE` | 搜索某种模式 | +| `%` | 表示任意字符出现任意次数 | +| `_` | 表示任意字符出现一次 | +| `[]` | 必须匹配指定位置的一个字符 | + +### 07 丨什么是 SQL 函数?为什么使用 SQL 函数可能会带来问题? + +- 数学函数 +- 字符串函数 +- 日期函数 +- 转换函数 +- 聚合函数 + +### 08 丨什么是 SQL 的聚集函数,如何利用它们汇总表的数据? + +聚合函数 + +| 函 数 | 说 明 | +| :-------: | :--------------: | +| `AVG()` | 返回某列的平均值 | +| `COUNT()` | 返回某列的行数 | +| `MAX()` | 返回某列的最大值 | +| `MIN()` | 返回某列的最小值 | +| `SUM()` | 返回某列值之和 | + +### 09 丨子查询:子查询的种类都有哪些,如何提高子查询的性能? + +EXISTS、IN、ANY、ALL、SOME + +### 10 丨常用的 SQL 标准有哪些,在 SQL92 中是如何使用连接的? + +内连接(INNER JOIN) + +自连接(`=`) + +自然连接(NATURAL JOIN) + +外连接(OUTER JOIN) + +左连接(LEFT JOIN) + +右连接(RIGHT JOIN) + +### 11 丨 SQL99 是如何使用连接的,与 SQL92 的区别是什么? + +### 12 丨视图在 SQL 中的作用是什么,它是怎样工作的? + +> 视图是基于 SQL 语句的结果集的可视化的表。**视图是虚拟的表,本身不存储数据,也就不能对其进行索引操作**。对视图的操作和对普通表的操作一样。 + +视图的作用: + +- 简化复杂的 SQL 操作,比如复杂的连接。 +- 只使用实际表的一部分数据。 +- 通过只给用户访问视图的权限,保证数据的安全性。 +- 更改数据格式和表示。 + +### 13 丨什么是存储过程,在实际项目中用得多么? + +存储过程的英文是 Stored Procedure。它可以视为一组 SQL 语句的批处理。一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。 + +存储过程的优点: + +- **执行效率高**:一次编译多次使用。 +- **安全性强**:在设定存储过程的时候可以设置对用户的使用权限,这样就和视图一样具有较强的安全性。 +- **可复用**:将代码封装,可以提高代码复用。 +- **性能好** + - 由于是预先编译,因此具有很高的性能。 + - 一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率。 + +存储过程的缺点: + +- **可移植性差**:存储过程不能跨数据库移植。由于不同数据库的存储过程语法几乎都不一样,十分难以维护(不通用)。 +- **调试困难**:只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。 +- **版本管理困难**:比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。 +- **不适合高并发的场景**:高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护,增加数据库的压力,显然就不适用了。 + +> _综上,存储过程的优缺点都非常突出,是否使用一定要慎重,需要根据具体应用场景来权衡_。 -### 性能优化维度 +### 14 丨什么是事务处理,如何使用 COMMIT 和 ROLLBACK 进行操作? -我个人理解的性能优化维度与文章有所不同: +ACID: -- 选择适合的数据库 +1. A,也就是原子性(Atomicity)。原子的概念就是不可分割,你可以把它理解为组成物质的基本单位,也是我们进行数据处理操作的基本单位。 +2. C,就是一致性(Consistency)。一致性指的就是数据库在进行事务操作后,会由原来的一致状态,变成另一种一致的状态。也就是说当事务提交后,或者当事务发生回滚后,数据库的完整性约束不能被破坏。 +3. I,就是隔离性(Isolation)。它指的是每个事务都是彼此独立的,不会受到其他事务的执行影响。也就是说一个事务在提交之前,对其他事务都是不可见的。 +4. 最后一个 D,指的是持久性(Durability)。事务提交之后对数据的修改是持久性的,即使在系统出故障的情况下,比如系统崩溃或者存储介质发生故障,数据的修改依然是有效的。因为当事务完成,数据库的日志就会被更新,这时可以通过日志,让系统恢复到最后一次成功的更新状态。 + +事务的控制语句: + +1. START TRANSACTION 或者 BEGIN,作用是显式开启一个事务。 +2. COMMIT:提交事务。当提交事务后,对数据库的修改是永久性的。 +3. ROLLBACK 或者 ROLLBACK TO [SAVEPOINT],意为回滚事务。意思是撤销正在进行的所有没有提交的修改,或者将事务回滚到某个保存点。 +4. SAVEPOINT:在事务中创建保存点,方便后续针对保存点进行回滚。一个事务中可以存在多个保存点。 +5. RELEASE SAVEPOINT:删除某个保存点。 +6. SET TRANSACTION,设置事务的隔离级别。 + +### 15 丨初识事务隔离:隔离的级别有哪些,它们都解决了哪些异常问题? + +事务隔离级别从低到高分别是:读未提交(READ UNCOMMITTED )、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和可串行化(SERIALIZABLE)。 + +### 16 丨游标:当我们需要逐条处理数据时,该怎么做? + +### 17 丨如何使用 Python 操作 MySQL? + +略 + +### 18 丨 SQLAlchemy:如何使用 PythonORM 框架来操作 MySQL? + +略 + +### 19 丨基础篇总结:如何理解查询优化、通配符以及存储过程? + +## 第二章:SQL 性能优化篇 + +### 20 丨当我们思考数据库调优的时候,都有哪些维度可以选择? + +我的理解: + +- 选择合适数据库 - 配置优化 - 硬件优化 - 优化表设计 @@ -143,7 +301,7 @@ SELECT * FROM world.country LIMIT 2, 3; - 使用缓存 - 读写分离+分库分表 -### 范式设计 +### 21 丨范式设计:数据表的范式有哪些,3NF 指的是什么? 范式定义: @@ -154,7 +312,10 @@ SELECT * FROM world.country LIMIT 2, 3; **范式化的目标是尽力减少冗余列,节省空间**。 +### 22 丨反范式设计:3NF 有什么不足,为什么有时候需要反范式设计? + **反范式化的目标是适当增加冗余列,以避免关联查询**。 + 范式化优点 - 更节省空间 @@ -165,11 +326,9 @@ SELECT * FROM world.country LIMIT 2, 3; - 增加了关联查询,而关联查询代价很高 -### 索引的适用场景 - -我认为,文章对于索引的适用场景讲解的不好。应该先讲清楚索引的优点和缺点,再结合其特性,来阐述适用场景。 +### 23 丨索引的概览:用还是不用索引,这是一个问题 -#### 索引的优缺点 +> 索引的优缺点 **索引的优点** @@ -183,7 +342,7 @@ SELECT * FROM world.country LIMIT 2, 3; - 占用额外物理空间 - 写操作时很可能需要更新索引,导致数据库的写操作性能降低 -#### 索引的适用场景 +> 索引的适用场景 **适用场景** @@ -199,75 +358,123 @@ SELECT * FROM world.country LIMIT 2, 3; - 非常小的表(比如不到 1000 行):简单的全表扫描更高效 - 特大型的表:索引的代价很高昂,可以用分区或 Nosql -### 索引使用什么数据结构 +### 24 丨索引的原理:我们为什么用 B+树来做索引? -索引常见的数据结构 +磁盘的 I/O 操作次数对索引的使用效率至关重要。虽然传统的二叉树数据结构查找数据的效率高,但很容易增加磁盘 I/O 操作的次数,影响索引使用的效率。因此在构造索引的时候,我们更倾向于采用“矮胖”的数据结构。 -(1)哈希 +B 树和 B+ 树都可以作为索引的数据结构,在 MySQL 中采用的是 B+ 树,B+ 树在查询性能上更稳定,在磁盘页大小相同的情况下,树的构造更加矮胖,所需要进行的磁盘 I/O 次数更少,更适合进行关键字的范围查询。 -只能用于等值查询。 +### 25 丨 Hash 索引的底层原理是什么? Mysql 中,只有 Memory 存储引擎显示支持哈希索引。 -哈希结构的缺点 +✔️️️️️ 哈希索引的**优点**: + +- 因为索引数据结构紧凑,所以**查询速度非常快**。 + +❌ 哈希索引的**缺点**: -- 哈希索引数据不是按照索引值顺序存储的——无法用于排序 -- 哈希索引使用索引列的全部内容来进行哈希计算的——不支持联合索引的最左侧原则(即联合索引的部分索引) -- 只支持等值比较查询 - - 不支持范围查询 - - 不支持模糊查询 -- 可能出现哈希冲突 - - 出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行 - - 如果哈希冲突多的话,维护索引的代价会很高 +- **只支持等值比较查询** - 包括 `=`、`IN()`、`<=>`。 + - **不支持范围查询**,如 `WHERE price > 100`。 + - **不支持模糊查询**,如 `%` 开头。 +- **无法用于排序** - 因为 Hash 索引指向的数据是无序的,因此无法起到排序优化的作用。 +- **不支持联合索引的最左侧原则** - 对于联合索引来说,Hash 索引在计算 Hash 值的时候是将索引键合并后再一起计算 Hash 值,所以不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用。例如:在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A,无法使用该索引。 +- **不能用索引中的值来避免读取行** - 因为哈希索引只包含哈希值和行指针,不存储字段,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响不大。 +- 哈希索引有**可能出现哈希冲突** + - 出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。 + - 如果哈希冲突多的话,维护索引的代价会很高。 -(2)B 树 +> 提示:因为种种限制,所以哈希索引只适用于特定的场合。而一旦使用哈希索引,则它带来的性能提升会非常显著。 -非叶子节点包含索引和数据。 +### 26 丨索引的使用原则:如何通过索引让 SQL 查询效率最大化? -(3)空间数据索引(R 树) +✔️️️️ 什么情况**适用**索引? -(4)全文索引 +- **字段的数值有唯一性的限制**,如用户名。 +- **频繁作为 `WHERE` 条件或 `JOIN` 条件的字段,尤其在数据表大的情况下** +- **频繁用于 `GROUP BY` 或 `ORDER BY` 的字段**。将该字段作为索引,查询时就无需再排序了,因为 B+ 树 +- **DISTINCT 字段需要创建索引**。 -### 从数据页的角度理解 B+树查询 +❌ 什么情况**不适用**索引? + +- **频繁写操作**( `INSERT`/`UPDATE`/`DELETE` ),也就意味着需要更新索引。 +- **列名不经常出现在 `WHERE` 或连接(`JOIN`)条件中**,也就意味着索引会经常无法命中,没有意义,还增加空间开销。 +- **非常小的表**,对于非常小的表,大部分情况下简单的全表扫描更高效。 +- **特大型的表**,建立和使用索引的代价将随之增长。可以考虑使用分区技术或 Nosql。 + +索引失效的场景: + +- 对索引使用左模糊匹配 +- 对索引使用表达式或函数 +- 对索引隐式类型转换 +- 联合索引不遵循最左匹配原则 +- 索引列判空 +- WHERE 子句中的 OR 前后条件存在非索引列 + +### 27 丨从数据页的角度理解 B+树查询 **在数据库中,不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说,数据库管理存储空间的基本单位是页(Page)。** -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720055715.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220720055715.png) 一个表空间包括了一个或多个段,一个段包括了一个或多个区,一个区包括了多个页,而一个页中可以有多行记录: -区(Extent)是比页大一级的存储结构,在 InnoDB 存储引擎中,一个区会分配 64 个连续的页。因为 InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64\*16KB=1MB。 +- 页是数据库存储的最小单位。 +- 区(Extent)是比页大一级的存储结构,在 InnoDB 存储引擎中,一个区会分配 64 个连续的页。因为 InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64\*16KB=1MB。 + +- 段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在 InnoDB 中是连续的 64 个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。 -段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在 InnoDB 中是连续的 64 个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。 +- 表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。 -表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。 +### 28 丨从磁盘 I/O 的角度理解 SQL 查询的成本 -### 从磁盘 I/O 的角度理解 SQL 查询的成本 +磁盘 I/O 耗时远大于内存,因此数据库会采用缓冲池的方式提升页的查找效率。 + +SQL 查询是一个动态的过程,从页加载的角度来看: 1. 位置决定效率。如果页就在数据库缓冲池中,那么效率是最高的,否则还需要从内存或者磁盘中进行读取,当然针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多。 2. 批量决定效率。如果我们从磁盘中对单一页进行随机读,那么效率是很低的(差不多 10ms),而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取。 -### 锁 +### 29 丨为什么没有理想的索引? + +略 + +### 30 丨锁:悲观锁和乐观锁是什么? + +基于加锁方式分类,Mysql 可以分为悲观锁和乐观锁。 + +- **悲观锁** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作 + - 在查询完数据的时候就把事务锁起来,直到提交事务(`COMMIT`) + - 实现方式:**使用数据库中的锁机制**。 +- **乐观锁** - 假设最好的情况——每次访问数据时,都假设数据不会被其他线程修改,不必加锁。只在更新的时候,判断一下在此期间是否有其他线程更新该数据。 + - 实现方式:**更新数据时,先使用版本号机制或 CAS 算法检查数据是否被修改**。 -- **行锁**就是按照行的粒度对数据进行锁定。锁定力度小,发生锁冲突概率低,可以实现的并发度高,但是对于锁的开销比较大,加锁会比较慢,容易出现死锁情况。 -- **页锁**就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。 -- **表锁**就是对数据表进行锁定,锁定粒度很大,同时发生锁冲突的概率也会较高,数据访问的并发度低。不过好处在于对锁的使用开销小,加锁会很快。 +### 31 丨为什么大部分 RDBMS 都会支持 MVCC? -不同的数据库和存储引擎支持的锁粒度不同: +MVCC 的核心就是 Undo Log+ Read View -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720062633.png) +- Undo Log 保存数据的历史版本,实现多版本的管理; +- 通过 Read View 原则来决定数据是否显示; +- 时针对不同的隔离级别,Read View 的生成策略不同,也就实现了不同的隔离级别 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720063540.png) +### 32 丨查询优化器是如何工作的? -### 数据库工作流 +MySQL 整个查询执行过程,总的来说分为 6 个步骤,分别对应 6 个组件: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720093559.png) +1. **连接器** - 客户端和 MySQL 服务器建立连接;连接器负责跟客户端建立连接、获取权限、维持和管理连接。 +2. **查询缓存** - MySQL 服务器首先检查查询缓存,如果命中缓存,则立刻返回结果。否则进入下一阶段。 +3. **分析器** - MySQL 服务器进行 SQL 分析:语法分析、词法分析。 +4. **优化器** - MySQL 服务器用优化器生成对应的执行计划。 +5. **执行器** - MySQL 服务器根据执行计划,调用存储引擎的 API 来执行查询。 +6. **返回结果** - MySQL 服务器将结果返回给客户端,同时缓存查询结果。 -### 如何使用性能分析工具定位 SQL 执行慢的原因? +### 33 丨如何使用性能分析工具定位 SQL 执行慢的原因? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720093823.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220720093823.png) -### 主从同步 +### 34 丨答疑篇:关于索引以及缓冲池的一些解惑 + +### 35 丨数据库主从同步的作用是什么,如何解决数据不一致问题? Mysql 支持两种复制:基于行的复制和基于语句的复制。 @@ -280,7 +487,7 @@ Mysql 支持两种复制:基于行的复制和基于语句的复制。 - **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。
- +
如何解决主从同步时的数据一致性问题? @@ -301,11 +508,11 @@ Mysql 支持两种复制:基于行的复制和基于语句的复制。 在一个复制组内有多个节点组成,它们各自维护了自己的数据副本,并且在一致性协议层实现了原子消息和全局有序消息,从而保证组内数据的一致性。 -### SQL 注入 +### 36 丨数据库没有备份,没有使用 Binlog 的情况下,如何恢复数据? -#### 概念 +### 37 丨 SQL 注入:你的 SQL 是如何被注入的? -**`SQL 注入攻击(SQL injection)`**,是发生于应用程序之数据层的安全漏洞。简而言之,是在输入的字符串之中注入 SQL 指令,在设计不良的程序当中忽略了检查,那么这些注入进去的指令就会被数据库服务器误认为是正常的 SQL 指令而运行,因此遭到破坏或是入侵。 +**SQL 注入攻击(SQL injection)**,是发生于应用程序之数据层的安全漏洞。简而言之,是在输入的字符串之中注入 SQL 指令,在设计不良的程序当中忽略了检查,那么这些注入进去的指令就会被数据库服务器误认为是正常的 SQL 指令而运行,因此遭到破坏或是入侵。 攻击示例: @@ -358,7 +565,7 @@ MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统 虽然以上的例子是针对某一特定的数据库系统的,但是这并不代表不能对其它数据库系统实施类似的攻击。针对这种安全漏洞,只要使用不同方法,各种数据库都有可能遭殃。 -#### 攻击手段和目的 +> 攻击手段和目的 - 数据表中的数据外泄,例如个人机密数据,账户数据,密码等。 - 数据结构被黑客探知,得以做进一步攻击(例如 `SELECT * FROM sys.tables`)。 @@ -367,33 +574,41 @@ MSSQL 服务器会执行这条 SQL 语句,包括它后面那个用于向系统 - 经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统(例如 xp_cmdshell "net stop iisadmin"可停止服务器的 IIS 服务)。 - 破坏硬盘数据,瘫痪全系统(例如 xp_cmdshell "FORMAT C:")。 -#### 应对手段 +> 应对手段 - **使用参数化查询** - 建议使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到 SQL 语句中,即不要直接拼接 SQL 语句。例如使用 database/sql 里面的查询函数 `Prepare` 和 `Query` ,或者 `Exec(query string, args ...interface{})`。 - **单引号转换** - 在组合 SQL 字符串时,先针对所传入的参数进行字符替换(将单引号字符替换为连续 2 个单引号字符)。 -> :point_right: 参考阅读: -> -> - [Wiki 词条 - SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A) -> - [避免 SQL 注入](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/09.4.md) -> - [实例讲解 SQL 注入攻击](http://blog.jobbole.com/83092/) +## 第三章:认识 DBMS + +> 内容对我意义不大,略 + +### 38 丨如何在 Excel 中使用 SQL 语言? + +### 39 丨 WebSQL:如何在 H5 中存储一个本地数据库? + +### 40 丨 SQLite:为什么微信用 SQLite 存储聊天记录? -## 认识 DBMS +### 41 丨初识 Redis:Redis 为什么会这么快? -内容对我意义不大,略 +### 42 丨如何使用 Redis 来实现多用户抢票问题 -## SQL 项目实战 +### 43 丨如何使用 Redis 搭建玩家排行榜? -### 数据清洗 +### 44 丨 DBMS 篇总结和答疑:用 SQLite 做词云 + +## 第四章:SQL 项目实战 + +### 45 丨数据清洗:如何使用 SQL 对数据进行清洗? SQL 可以帮我们进行数据处理,总的来说可以分成 OLTP 和 OLAP 两种方式。 -- **OLTP**:称之为联机事务处理。对数据进行增删改查,SQL 查询优化,事务处理等就属于 OLTP 的范畴。它对实时性要求高,需要将用户的数据有效地存储到数据库中,同时有时候针对互联网应用的需求,我们还需要设置数据库的主从架构保证数据库的高并发和高可用性。 -- **OLAP**:称之为联机分析处理。它是对已经存储在数据库中的数据进行分析,帮我们得出报表,指导业务。它对数据的实时性要求不高,但数据量往往很大,存储在数据库(数据仓库)中的数据可能还存在数据质量的问题,比如数据重复、数据中有缺失值,或者单位不统一等,因此在进行数据分析之前,首要任务就是对收集的数据进行清洗,从而保证数据质量。 +- **OLTP**:称之为**联机事务处理**。对数据进行增删改查,SQL 查询优化,事务处理等就属于 OLTP 的范畴。它对实时性要求高,需要将用户的数据有效地存储到数据库中,同时有时候针对互联网应用的需求,我们还需要设置数据库的主从架构保证数据库的高并发和高可用性。 +- **OLAP**:称之为**联机分析处理**。它是对已经存储在数据库中的数据进行分析,帮我们得出报表,指导业务。它对数据的实时性要求不高,但数据量往往很大,存储在数据库(数据仓库)中的数据可能还存在数据质量的问题,比如数据重复、数据中有缺失值,或者单位不统一等,因此在进行数据分析之前,首要任务就是对收集的数据进行清洗,从而保证数据质量。 -### 数据集成 +### 46 丨数据集成:如何对各种数据库进行集成和转换? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720142031.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220720142031.png) ETL 是英文 Extract、Transform 和 Load 的缩写,也就是将数据从不同的数据源进行抽取,然后通过交互转换,最终加载到目的地的过程。 @@ -401,6 +616,10 @@ ETL 是英文 Extract、Transform 和 Load 的缩写,也就是将数据从不 - 在 Transform 数据转换的过程中,我们可以使用一些数据转换的组件,比如说数据字段的映射、数据清洗、数据验证和数据过滤等,这些模块可以像是在流水线上进行作业一样,帮我们完成各种数据转换的需求,从而将不同质量,不同规范的数据进行统一。 - 在 Load 数据加载的过程中,我们可以将转换之后的数据加载到目的地,如果目标是 RDBMS,我们可以直接通过 SQL 进行加载,或者使用批量加载的方式进行加载。 +### 47 丨如何利用 SQL 对零售数据进行分析? + +略 + # 参考资料 -- [极客时间 - SQL 必知必会](https://time.geekbang.org/column/intro/192) \ No newline at end of file +- [SQL 必知必会](https://time.geekbang.org/column/intro/192) diff --git "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/03.MySQL\345\256\236\346\210\23045\350\256\262.md" "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/03.MySQL\345\256\236\346\210\23045\350\256\262.md" index d2a617dc82..a7be6cd34b 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/03.MySQL\345\256\236\346\210\23045\350\256\262.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/03.MySQL\345\256\236\346\210\23045\350\256\262.md" @@ -1,6 +1,7 @@ --- title: 《MySQL 实战 45 讲》笔记 date: 2022-07-20 19:20:08 +order: 03 categories: - 笔记 - 数据库 @@ -14,7 +15,7 @@ permalink: /pages/1ee347/ ## 基础架构:一条 SQL 查询语句是如何执行的? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720195101.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220720195101.png) 1. **连接器**:连接器负责跟客户端建立连接、获取权限、维持和管理连接。 2. **查询缓存**:命中缓存,则直接返回结果。弊大于利,因为失效非常频繁——任何更新都会清空查询缓存。 @@ -44,7 +45,7 @@ permalink: /pages/1ee347/ InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720203348.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220720203348.png) write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。 @@ -70,7 +71,7 @@ redo log 和 binlog 的差异: 4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。 5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220720210120.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220720210120.png) ### 两阶段提交 @@ -87,7 +88,7 @@ redo log 和 binlog 的差异: ## 事务隔离:为什么你改了我还看不见? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220721072721.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220721072721.png) ## 深入浅出索引 @@ -222,7 +223,7 @@ InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。 而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726083656.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726083656.png) 图中虚线框里是同一行数据的 4 个版本,当前最新版本是 V4,k 的值是 22,它是被 transaction id 为 25 的事务更新的,因此它的 row trx_id 也是 25。 @@ -240,7 +241,7 @@ InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。 这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726085300.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726085300.png) 这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能: @@ -252,13 +253,13 @@ InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。 **InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。** -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726085703.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726085703.png) ### 更新逻辑 **更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。** -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726090537.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726090537.png) **事务的可重复读的能力是怎么实现的?** @@ -298,11 +299,11 @@ change buffer 只限于用在普通索引的场景下,而不适用于唯一索 ### change buffer 和 redo log -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726192619.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726192619.png) 图 - 带 change buffer 的更新过程 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726192712.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726192712.png) 图 - 带 change buffer 的读过程 @@ -326,9 +327,9 @@ MySQL 在真正开始执行语句之前,并不能精确地知道满足这个 ## 怎么给字符串字段加索引? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726194835.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726194835.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726194844.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726194844.png) **使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。** @@ -420,7 +421,7 @@ delete 命令其实只是把记录的位置,或者数据页标记为了“可 可以使用 `alter table A engine=InnoDB` 命令来重建表。MySQL 会自动完成转存数据、交换表名、删除旧表的操作。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726203135.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726203135.png) 显然,花时间最多的步骤是往临时表插入数据的过程,如果在这个过程中,有新的数据要写入到表 A 的话,就会造成数据丢失。因此,在整个 DDL 过程中,表 A 中不能有更新。也就是说,这个 DDL 不是 Online 的。 @@ -432,7 +433,7 @@ delete 命令其实只是把记录的位置,或者数据页标记为了“可 4. 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表 A 相同的数据文件,对应的就是图中 state3 的状态; 5. 用临时文件替换表 A 的数据文件。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220726203250.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220726203250.png) 对于一个大表来说,Online DDL 最耗时的过程就是拷贝数据到临时表的过程,这个步骤的执行期间可以接受增删改操作。所以,相对于整个 DDL 过程来说,锁的时间非常短。对业务来说,就可以认为是 Online 的。 @@ -455,7 +456,7 @@ optimize table、analyze table 和 alter table 这三种方式重建表的区别 因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220727084306.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220727084306.png) InnoDB 是索引组织表,主键索引树的叶子节点是数据,而普通索引树的叶子节点是主键值。所以,普通索引树比主键索引树小很多。对于 count(\*) 这样的操作,遍历哪个索引树得到的结果逻辑上都是一样的。因此,MySQL 优化器会找到最小的那棵树来遍历。 @@ -504,7 +505,7 @@ select city,name,age from t where city='杭州' order by name limit 1000; 6. 对 sort_buffer 中的数据按照字段 name 做快速排序; 7. 按照排序结果取前 1000 行返回给客户端。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220728090300.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220728090300.png) 按 name 排序”这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数 sort_buffer_size。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。 @@ -518,7 +519,7 @@ select city,name,age from t where city='杭州' order by name limit 1000; 取行数据时,不取出整行,而只是取出 id 和用于排序的字段。当排序结束后,再根据 id 取出要查询的字段返回给客户端。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220728090919.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220728090919.png) ### 全字段排序 VS rowid 排序 @@ -627,7 +628,7 @@ WHERE d.tradeid = l.tradeid AND d.id = 2; 使用 show processlist 命令查看 Waiting for table metadata lock 的示意图 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220801160916.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220801160916.png) 出现**这个状态表示的是,现在有一个线程正在表 t 上请求或者持有 MDL 写锁,把 select 语句堵住了。** @@ -730,7 +731,7 @@ WHERE d.tradeid = l.tradeid AND d.id = 2; binglog 写入逻辑:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 中,并清空 binlog cache。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220802060429.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220802060429.png) 一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。 @@ -784,7 +785,7 @@ WAL 机制主要得益于两个方面: MySQL 主备切换流程 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220802062247.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220802062247.png) 客户端的读写都直接访问主库,备库只是将主库的更新都同步过来,到本地执行。 @@ -875,13 +876,13 @@ show slave status 命令可用于显示备库延迟(seconds_behind_master) 我们把这个切换流程,暂时称作可用性优先流程。这个切换流程的代价,就是可能出现数据不一致的情况。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220802065420.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220802065420.png) ## 备库为什么会延迟好几个小时 ### 按表分发策略 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220802070053.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220802070053.png) 每个 worker 线程对应一个 hash 表,用于保存当前正在这个 worker 的“执行队列”里的事务所涉及的表。hash 表的 key 是“库名. 表名”,value 是一个数字,表示队列中有多少个事务修改这个表。 @@ -936,7 +937,7 @@ MariaDB 的并行复制策略利用的就是这个特性: ## 主库出问题了,从库怎么办? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220803070027.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220803070027.png) A 和 A’互为主备, 从库 B、C、D 指向的是主库 A @@ -988,7 +989,7 @@ master_auto_position=1 还有一种架构是,在 MySQL 和客户端之间有一个中间代理层 proxy,客户端只连接 proxy, 由 proxy 根据请求类型和上下文决定请求的分发路由。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220803071504.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220803071504.png) 客户端直连 vs. 带 proxy 的读写分离 @@ -1316,7 +1317,7 @@ select v from temp_ht order by t_modified desc limit 100; 得到结果。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220828152038.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220828152038.png) **在实践中,我们往往会发现每个分库的计算量都不饱和,所以会直接把临时表 temp_ht 放到 32 个分库中的某一个上。** diff --git "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/11.\346\243\200\347\264\242\346\212\200\346\234\257\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/11.\346\243\200\347\264\242\346\212\200\346\234\257\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" index 7d4fe9f4c4..b3051674f7 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/11.\346\243\200\347\264\242\346\212\200\346\234\257\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/12.\346\225\260\346\215\256\345\272\223/11.\346\243\200\347\264\242\346\212\200\346\234\257\346\240\270\345\277\20320\350\256\262\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《检索技术核心 20 讲》笔记 date: 2022-03-04 20:03:00 +order: 11 categories: - 笔记 - 数据库 @@ -162,7 +163,7 @@ LSM 树的这些特点,使得它相对于 B+ 树,在写入性能上有大幅 在滚动合并法中,当内存中的增量索引增长到一定体量时,我们会用再合并法将它合并到磁 盘上当天的天级索引文件中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220316134834.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220316134834.png) ## 索引拆分 diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/01.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\270\200.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/01.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\270\200.md" index c89e6ef2be..d392002dbf 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/01.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\270\200.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/01.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\270\200.md" @@ -1,6 +1,7 @@ --- title: 《数据密集型应用系统设计》笔记一之分布式数据系统 date: 2021-08-26 23:32:00 +order: 01 categories: - 笔记 - 分布式 @@ -56,7 +57,7 @@ permalink: /pages/7e2a8f/ 2. 其他副本则全部称为从副本(或称为从节点)。主副本把新数据写入本地存储后,然后将数据更改作为复制的日志或更改流发送给所有从副本。每个从副本获得更改日志之后将其应用到本地,且严格保持与主副本相同的写入顺序。 3. 客户端从数据库中读数据时,可以在主副本或者从副本上执行查询。再次强调,只有主副本才可以接受写请求:从客户端的角度来看,从副本都是只读的。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220302202101.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220302202101.png) 典型应用: @@ -68,7 +69,7 @@ permalink: /pages/7e2a8f/ 基本流程是,客户将更新请求发送给主节点,主节点接收到请求,接下来将数据更新转发给从节点。最后,由 主节点来通知客户更新完成。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220302202158.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220302202158.png) 通常情况下, 复制速度会非常快,例如多数数据库系统可以在一秒之内完成所有从节点的更新。但是,系统其 实并没有保证一定会在多段时间内完成复制。有些情况下,从节点可能落后主节点几分钟甚至更长时间,例如,由于从节点刚从故障中恢复,或者系统已经接近最大设计上限,或者节点之间的网络出现问题。 @@ -184,7 +185,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 然而对于异步复制存在这样一个问题,如图 5-3 所示,用户在写人不久即查看数据,则新数据可能尚未到达从节点。对用户来讲, 看起来似乎是刚刚提交的数据丢失了,显然用户不会高兴。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220302204836.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220302204836.png) 对于这种情况,我们需要强一致性。如何实现呢?有以下方案: @@ -201,7 +202,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 #### 单调读 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303093658.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303093658.png) 用户看到了最新内窑之后又读到了过期的内容,好像时间被回拨, 此时需要单调读一致性。 @@ -310,7 +311,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 一且找到合适的关键宇 H 合希函数,就可以为每个分区分配一个哈希范围(而不是直接作用于关键宇范围),关键字根据其哈希值的范围划分到不同的分区中。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303105925.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303105925.png) 这种方总可以很好地将关键字均匀地分配到多个分区中。分区边界可以是均匀间隔,也可以是伪随机选择( 在这种情况下,该技术有时被称为一致性哈希) 。 @@ -330,7 +331,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 #### 基于文档分区的二级索引 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303111528.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303111528.png) 在这种索引方法中,每个分区完全独立,各自维护自己的二级索引,且只负责自己分区内的文档而不关心其他分区中数据。每当需要写数据库时,包括添加,删除或更新文档等,只需要处理包含目标文档 ID 的那一个分区。因此文档分区索引也被称为本地索引,而不是全局索引。 @@ -342,7 +343,7 @@ PostgreSQL 、Oracle 以及其他系统等支持这种复制方式。其主要 为避免成为瓶颈,不能将全局索引存储在一个节点上,否则就破坏了设计分区均衡的目标。所以,全局索引也必须进行分区,且可以与数据关键字采用不同的分区策略。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220303112708.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220303112708.png) 可以直接通过关键词来全局划分索引,或者对其取哈希值。直接分区的好处是可以支持高效的区间查询;而采用哈希的方式则可以更均句的划分分区。 @@ -412,11 +413,11 @@ Cassandra 和 Ketama 则采用了第三种方式,使分区数与集群节点 2. 将所有客户端的请求都发送到一个路由层,由后者负责将请求转发到对应的分区节点上。路由层本身不处理任何请求,它仅充一个分区感知的负载均衡器。 3. 客户端感知分区和节点分配关系。此时,客户端可以直接连接到目标节点,而不需要任何中介。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220304120137.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220304120137.png) 许多分布式数据系统依靠独立的协调服务(如 ZooKeeper )跟踪集群范围内的元数据。每个节点都向 ZooKeeper 中注册自己, ZooKeeper 维护了分区到节点的最终映射关系。其他参与者(如路由层或分区感知的客户端)可以向 ZooKeeper 订阅此信息。一旦分区发生了改变,或者添加、删除节点, ZooKeeper 就会主动通知路由层,这样使路由信息保持最新状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220304163629.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220304163629.png) ## 事务 diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/02.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\272\214.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/02.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\272\214.md" index cd51844836..28702e5ce2 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/02.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\272\214.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/00.\345\210\206\345\270\203\345\274\217\347\273\274\345\220\210/02.\346\225\260\346\215\256\345\257\206\351\233\206\345\236\213\345\272\224\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241\347\254\224\350\256\260\344\272\214.md" @@ -1,6 +1,7 @@ --- title: 《数据密集型应用系统设计》笔记二之数据系统基础 date: 2021-08-26 23:32:00 +order: 02 categories: - 笔记 - 分布式 diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\215\217\350\256\256\344\270\216\347\256\227\346\263\225\345\256\236\346\210\230\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\215\217\350\256\256\344\270\216\347\256\227\346\263\225\345\256\236\346\210\230\347\254\224\350\256\260.md" index 3b0f7b7e9a..0726e6a25b 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\215\217\350\256\256\344\270\216\347\256\227\346\263\225\345\256\236\346\210\230\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/01.\345\210\206\345\270\203\345\274\217\345\215\217\350\256\256\344\270\216\347\256\227\346\263\225\345\256\236\346\210\230\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《分布式协议与算法实战》笔记 date: 2022-06-27 11:49:01 +order: 01 categories: - 笔记 - 分布式 @@ -35,7 +36,7 @@ permalink: /pages/53d3f7/ 上述的故事可以映射到分布式系统中,_将军代表分布式系统中的节点;信使代表通信系统;叛徒代表故障或异常_。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210704104211.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210704104211.png) ### 问题分析 @@ -51,13 +52,13 @@ permalink: /pages/53d3f7/ **示例一、叛徒人数为 1,将军人数为 3** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210704112012.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210704112012.png) 这个示例中,将军人数不满足 3m + 1,无法保证忠诚的副官都执行将军的命令。 **示例二、叛徒人数为 1,将军人数为 4** -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210704194815.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210704194815.png) 这个示例中,将军人数满足 3m + 1,无论是副官中有叛徒,还是将军是叛徒,都能保证忠诚的副官执行将军的命令。 @@ -69,7 +70,7 @@ CAP 是指:在一个分布式系统中, 一致性、可用性和分区容忍 - **可用性(A:Availability)**:分布式系统在面对各种异常时可以提供正常服务的能力 - **分区容忍性(P:Partition Tolerance)**:分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障 - + CAP 权衡 @@ -94,7 +95,7 @@ ACID 特性: - 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 - 可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/database/RDB/数据库ACID.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/database/RDB/数据库ACID.png) 在分布式系统中实现 ACID 比单机复杂的多。 @@ -119,7 +120,7 @@ BASE 特性 - **软状态(Soft State)**:指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在延时。 - **最终一致性(Eventually Consistent)**:最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能达到一致的状态。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/%E5%88%86%E5%B8%83%E5%BC%8F%E7%90%86%E8%AE%BA-BASE.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/%E5%88%86%E5%B8%83%E5%BC%8F%E7%90%86%E8%AE%BA-BASE.png) ## Paxos 算法 @@ -138,7 +139,7 @@ Paxos 算法运行在允许宕机故障的异步系统中,不要求可靠的 #### 角色 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20210528150700.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210528150700.png) - **提议者(Proposer)**:发出提案(Proposal),用于投票表决。Proposal 信息包括提案编号 (Proposal ID) 和提议的值 (Value)。在绝大多数场景中,集群中收到客户端请求的节点,才是提议者。这样做的好处是,对业务代码没有入侵性,也就是说,我们不需要在业务代码中实现算法逻辑。 - **决策者(Acceptor)**:对每个 Proposal 进行投票,若 Proposal 获得多数 Acceptor 的接受,则称该 Proposal 被批准。一般来说,集群中的所有节点都在扮演决策者的角色,参与共识协商,并接受和存储数据。 @@ -219,7 +220,7 @@ Raft 将一致性问题分解成了三个子问题: - **`Follower`** - 跟随者,**不会发送任何请求**,只是简单的 **响应来自 Leader 或者 Candidate 的请求**。 - **`Candidate`** - 参选者,选举新 Leader 时的临时角色。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131215742.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131215742.png) > :bulb: 图示说明: > @@ -229,7 +230,7 @@ Raft 将一致性问题分解成了三个子问题: #### 任期 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200131220742.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200131220742.png) Raft 把时间分割成任意长度的 **_`任期(Term)`_**,任期用连续的整数标记。每一段任期从一次**选举**开始。**Raft 保证了在一个给定的任期内,最多只有一个领导者**。 @@ -317,7 +318,7 @@ Raft 日志同步保证如下两点: #### 日志复制流程 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200201115848.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200201115848.png) 1. Leader 负责处理所有客户端的请求。 2. Leader 把请求作为日志条目加入到它的日志中,然后并行的向其他服务器发送 `AppendEntries RPC` 请求,要求 Follower 复制日志条目。 @@ -409,7 +410,7 @@ Raft 通过比较两份日志中最后一条日志条目的日志索引和 Term 当 Leader 要发送某个日志条目,落后太多的 Follower 的日志条目会被丢弃,Leader 会将快照发给 Follower。或者新上线一台机器时,也会发送快照给它。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200201220628.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200201220628.png) **生成快照的频率要适中**,频率过高会消耗大量 I/O 带宽;频率过低,一旦需要执行恢复操作,会丢失大量数据,影响可用性。推荐当日志达到某个固定的大小时生成快照。 @@ -423,7 +424,7 @@ Raft 通过比较两份日志中最后一条日志条目的日志索引和 Term **一致性哈希** 可以很好的解决 **稳定性问题**,可以将所有的 **存储节点** 排列在 **首尾相接** 的 `Hash` 环上,每个 `key` 在计算 `Hash` 后会 **顺时针** 找到 **临接** 的 **存储节点** 存放。而当有节点 **加入** 或 **退出** 时,仅影响该节点在 `Hash` 环上 **顺时针相邻** 的 **后续节点**。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/design/architecture/partition-consistent-hash.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/design/architecture/partition-consistent-hash.png) - **相同的请求**是指:一般在使用一致性哈希时,需要指定一个 key 用于 hash 计算,可能是: - 用户 ID @@ -599,7 +600,7 @@ ZAB 协议定义了两个可以**无限循环**的流程: 那么,ZooKeeper 是如何实现副本机制的呢?答案是:ZAB 协议的原子广播。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/zookeeper/zookeeper_3.png) ZAB 协议的原子广播要求: diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/02.\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\216\237\347\220\206\344\270\216\347\256\227\346\263\225\350\247\243\346\236\220\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/02.\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\216\237\347\220\206\344\270\216\347\256\227\346\263\225\350\247\243\346\236\220\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..0888a18442 --- /dev/null +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/01.\345\210\206\345\270\203\345\274\217\347\220\206\350\256\272/02.\345\210\206\345\270\203\345\274\217\346\212\200\346\234\257\345\216\237\347\220\206\344\270\216\347\256\227\346\263\225\350\247\243\346\236\220\347\254\224\350\256\260.md" @@ -0,0 +1,255 @@ +--- +title: 《分布式技术原理与算法解析》笔记 +date: 2023-06-07 13:49:02 +order: 02 +categories: + - 笔记 + - 分布式 + - 分布式理论 +tags: + - 分布式 + - 理论 +permalink: /pages/80055a/ +--- + +# 《分布式技术原理与算法解析》笔记 + +## 开篇词丨四纵四横,带你透彻理解分布式技术 + +## 分布式缘何而起:从单兵,到游击队,到集团军 + +## 分布式系统的指标:啥是分布式的三围 + +## 分布式互斥:有你没我,有我没你 + +## 分布式选举:国不可一日无君 + +## 分布式共识:存异求同 + +## 分布式事务:Allornothing + +## 分布式锁:关键重地,非请勿入 + +## 答疑篇:分布式技术是如何引爆人工智能的? + +## 分布式体系结构之集中式结构:一人在上,万人在下 + +## 分布式体系结构之非集中式结构:众生平等 + +## 分布式调度架构之单体调度:物质文明、精神文明一手抓 + +定义:单体调度是指,一个集群中只有一个节点运行调度进程,该节点对集群中的其他节点具有访问权限,可以搜集其他节点的资源信息、节点状态等进行统一管理,同时根据用户下发的任务对资源的需求,在调度器中进行任务与资源匹配,然后根据匹配结果将任务指派给其他节点。 + +架构:单体调度器也叫作集中式调度器,指的是使用中心化的方式去管理资源和调度任务。 + +特点:**单体调度器拥有全局资源视图和全局任务,可以很容易地实现对任务的约束并实施全局性的调度策略**。 + +单体调度代表:K8S、Borg 等。 + +## 分布式调度架构之两层调度:物质文明、精神文明两手抓 + +定义:在两层调度器中,资源的使用状态同时由中央调度器和第二层调度器管理,但中央调度器一般只负责宏观的、大规模的资源分配,业务压力比较小;第二层调度器负责任务与资源的匹配,因此第二层调度可以有多个,以支持不同的任务类型。 + +特点:解决了单体调度架构中,中央服务器的单点瓶颈问题;相较于单体调度而言,提升了调度效率;支持多种类型的任务。 + +两层调度代表:YARN、Mesos 等。 + +## 分布式调度架构之共享状态调度:物质文明、精神文明多手协商抓 + +定义:共享状态调度架构沿袭了单体架构的模式,通过将单体调度器分解为多个调度器,每个调度器都有全局的资源状态信息,从而实现最优的任务调度。 + +## 分布式通信之远程调用:我是你的千里眼 + +本地过程调用(Local Procedure Call, LPC),是指运行在同一台机器上的进程之间的互相通信。 + +远程过程调用(Remote Procedure Call, RPC),是指不同机器中运行的进程之间的相互通信,某一机器上运行的进程在不知道底层通信细节的情况下,就像访问本地服务一样,去调用远程机器上的服务。 + +## 分布式通信之发布订阅:送货上门 + +## 分布式通信之消息队列:货物自取 + +## CAP 理论:这顶帽子我不想要 + +CAP 是指:在一个分布式系统中, 一致性、可用性和分区容错性,最多只能同时满足其中两项。 + +- **一致性(C:Consistency)** - 多个数据副本是否能保持一致 +- **可用性(A:Availability)**- 分布式系统在面对各种异常时可以提供正常服务的能力 +- **分区容错性(P:Partition Tolerance)** - 分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障 + +在分布式系统中,分区容错性必不可少,因为需要总是假设网络是不可靠的;CAP 理论实际在是要在可用性和一致性之间做权衡。 + +- CP - 需要让所有节点下线成为不可用的状态,等待同步完成。 +- AP - 在同步过程中允许读取所有节点的数据,但是数据可能不一致。 + +## 分布式数据存储系统之三要素:顾客、导购与货架 + +数据的生产和消费 + +数据特征:结构化数据、半结构化数据、非结构化数据 + +分区和复制 + +## 数据分布方式之哈希与一致性哈希:“掐指一算”与“掐指两算”的事 + +分布式数据存储选型的考量维度: + +数据均匀:数据存储、访问尽量均衡 + +数据稳定:当数据存储集群扩容或缩容时,数据分布规则应尽量稳定,不要出现大范围的数据迁移。 + +节点异构性:应考虑集群中不同节点硬件配置的差异,将数据承载根据配置尽量均衡 + +## 分布式数据复制技术:分身有术 + +数据复制是指,如何让主备数据库保持数据一致的技术。 + +复制技术分类 + +- 同步 - 注重一致性(CP 模型)。数据更新时,主节点必须要同步所有从节点,才提交更新。 +- 异步 - 注重可用性(AP 模型)。数据更新时,主节点处理完后,直接提交更新;从节点异步进行数据的同步。 +- 半同步 - 采用折中处理。数据更新时,主节点同步部分从节点(通常为一个节点或一半节点)成功后,才提交更新。 + +很多分布式存储支持通过配置,切换复制策略,以满足不同场景的需要。 + +## 分布式数据之缓存技术:“身手钥钱”随身带 + +## 分布式高可靠之负载均衡:不患寡,而患不均 + +负载均衡(Load Balancing)是指将请求或流量均衡地分配到多个服务器或节点上,以实现资源的最优化利用和高效的响应速度。 + +负载均衡常见策略 + +- 随机负载均衡 + - 策略 - 将请求随机分发到候选服务器 + - 特点 - 调用量越大,负载越均衡 + - 适合场景 - 适合服务器硬件相同的场景 +- 轮询负载均衡 + - 策略 - 将请求依次分发到候选服务器 + - 特点 - 请求完全均匀分发 + - 场景 - 适合服务器硬件相同的场景 +- 最小活跃数负载均衡 + - 策略 - 将请求分发到连接数/请求数最少的候选服务器 + - 特点 - 根据候选服务器当前的请求连接数,动态分配 + - 适合场景 - 适用于对系统负载较为敏感或请求连接时长相差较大的场景 +- 哈希负载均衡 + - 策略 - 根据一个 key (可以是唯一 ID、IP 等),通过哈希计算得到一个数值,用该数值在候选服务器列表的进行取模运算,得到的结果便是选中的服务器 + - 特点 - 保证特定用户总是请求到相同的服务器,若服务器宕机,会话会丢失 + - 适合场景 - 可以保证同一 IP 的客户端的请求会转发到同一台服务器上,用来实现会话粘滞(Sticky Session) +- 一致性哈希负载均衡 + - 策略 - 相同的请求尽可能落到同一个服务器上。尽可能是指:服务器可能发生上下线,少数服务器的变化不应该影响大多数的请求。当某台候选服务器宕机时,原本发往该服务器的请求,会基于虚拟节点,平摊到其它候选服务器,不会引起剧烈变动。 + - 优点 - 加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。 + - 缺点 - 加减节点会造成哈希环中部分数据无法命中。当使用少量节点时,节点变化将大范围影响哈希环中数据映射,不适合少量数据节点的分布式方案。普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。 + - 适合场景 - 一致性哈希可以很好的解决稳定性问题,可以将所有的存储节点排列在首尾相接的 Hash 环上,每个 key 在计算 Hash 后会顺时针找到临接的存储节点存放。而当有节点加入或退出时,仅影响该节点在 Hash 环上顺时针相邻的后续节点。 + +## 分布式高可靠之流量控制:大禹治水,在疏不在堵 + +## 分布式高可用之故障隔离:当断不断,反受其乱 + +## 分布式高可用之故障恢复:知错能改,善莫大焉 + +## 答疑篇:如何判断并解决网络分区问题? + +## 知识串联:以购买火车票的流程串联分布式核心技术 + +## 搭建一个分布式实验环境:纸上得来终觉浅,绝知此事要躬行 + +## 特别放送丨那些你不能错过的分布式系统论文 + +### 分布式理论基础 + +[Time, Clocks, and the Ordering of Events in a Distributed System](https://lamport.azurewebsites.net/pubs/time-clocks.pdf) + +[The Byzantine Generals Problem](https://lamport.azurewebsites.net/pubs/byz.pdf) + +[Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services](https://www.comp.nus.edu.sg/~gilbert/pubs/BrewersConjecture-SigAct.pdf) + +CAP Twelve Years Later: How the “Rules” Have Changed + +BASE: An Acid Alternative + +A Simple Totally Ordered Broadcast Protocol + +Virtual Time and Global States of Distributed Systems + +### 分布式一致性算法 + +Paxos Made Simple + +Paxos Made Practical + +Paxos Made Live: An Engineering Perspective + +Raft: In Search of an Understandable Consensus Algorithm + +ZooKeeper: Wait-Free Coordination for Internet-Scale Systems + +Using Paxos to Build a Scalable, Consistent, and Highly Available Datastore +Impossibility of Distributed Consensus With One Faulty Process + +A Brief History of Consensus, 2PC and Transaction Commit + +Consensus in the Presence of Partial Synchrony + +### 分布式数据结构 + +Chord: A Scalable Peer-to-Peer Lookup Service for Internet Applications + +Pastry: Scalable, Distributed Object Location, and Routing for Large-Scale Peerto-Peer Systems + +Kademlia: A Peer-to-Peer Information System Based on the XOR Metric + +A Scalable Content-Addressable Network + +Ceph: A Scalable, High-Performance Distributed File System + +The Log-Structured-Merge-Tree + +HBase: A NoSQL Database + +Tango: Distributed Data Structure over a Shared Log + +### 分布式系统实战 + +The Google File System + +BigTable: A Distributed Storage System for Structured Data + +The Chubby Lock Service for Loosely-Coupled Distributed Systems + +Finding a Needle in Haystack: Facebook’s Photo Storage + +Windows Azure Storage: A Highly Available Cloud Storage Service with Strong Consistency + +Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing + +Scaling Distributed Machine Learning with the Parameter Server + +Dremel: Interactive Analysis of Web-Scale Datasets + +Pregel: A System for Large-Scale Graph Processing + +Spanner: Google’s Globally-Distributed Database + +Dynamo: Amazon’s Highly Available Key-value Store + +S4: Distributed Stream Computing Platform + +Storm @Twitter + +Large-scale Cluster Management at Google with Borg + +F1 - The Fault-Tolerant Distributed RDBMS Supporting Google’s Ad Business + +Cassandra: A Decentralized Structured Storage System + +MegaStore: Providing Scalable, Highly Available Storage for Interactive Services + +Dapper, a Large-Scale Distributed Systems Tracing Infrastructure + +Kafka: A distributed Messaging System for Log Processing + +Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases + +## 参考资料 + +- [分布式协议与算法实战](https://time.geekbang.org/column/intro/100046101) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC\345\256\236\346\210\230\344\270\216\346\240\270\345\277\203\345\216\237\347\220\206.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC\345\256\236\346\210\230\344\270\216\346\240\270\345\277\203\345\216\237\347\220\206\347\254\224\350\256\260.md" similarity index 82% rename from "source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC\345\256\236\346\210\230\344\270\216\346\240\270\345\277\203\345\216\237\347\220\206.md" rename to "source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC\345\256\236\346\210\230\344\270\216\346\240\270\345\277\203\345\216\237\347\220\206\347\254\224\350\256\260.md" index 7f5b228ed8..ff11be5a2e 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC\345\256\236\346\210\230\344\270\216\346\240\270\345\277\203\345\216\237\347\220\206.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/01.RPC\345\256\236\346\210\230\344\270\216\346\240\270\345\277\203\345\216\237\347\220\206\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《RPC 实战与核心原理》笔记 date: 2022-06-19 09:48:17 +order: 01 categories: - 笔记 - 分布式 @@ -14,21 +15,23 @@ permalink: /pages/4b43b4/ # 《RPC 实战与核心原理》笔记 +## 别老想着怎么用好 RPC 框架,你得多花时间琢磨原理 + 为什么要学习 RPC RPC 不仅是微服务的架构基础,实际上,只要涉及网络通信,就可能用到 RPC。 -例 1:大型分布式应用系统可能会依赖消息队列、分布式缓存、分布式数据库以及统一配置中心等,应用程序与依赖的这些中间件之间都可以通过 RPC 进行通信。比如 etcd,它作为一个统一的配置服务,客户端就是通过 gRPC 框架与服务端进行通信的。 +- 例 1:大型分布式应用系统可能会依赖消息队列、分布式缓存、分布式数据库以及统一配置中心等,应用程序与依赖的这些中间件之间都可以通过 RPC 进行通信。比如 etcd,它作为一个统一的配置服务,客户端就是通过 gRPC 框架与服务端进行通信的。 -例 2:我们经常会谈到的容器编排引擎 Kubernetes,它本身就是分布式的,Kubernetes 的 kube-apiserver 与整个分布式集群中的每个组件间的通讯,都是通过 gRPC 框架进行的。 +- 例 2:我们经常会谈到的容器编排引擎 Kubernetes,它本身就是分布式的,Kubernetes 的 kube-apiserver 与整个分布式集群中的每个组件间的通讯,都是通过 gRPC 框架进行的。 -RPC 是解决分布式系统通信问题的一大利器。 +**RPC 是解决分布式系统通信问题的一大利器**。 -## 核心原理 +## 核心原理:能否画张图解释下 RPC 的通信流程? ### 什么是 RPC? -RPC 的全称是 Remote Procedure Call,即远程过程调用。 +RPC 的全称是 Remote Procedure Call,即**远程过程调用**。 RPC 的作用体现在两个方面: @@ -37,15 +40,15 @@ RPC 的作用体现在两个方面: ### RPC 通信流程 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619100051.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619100051.png) -RPC 是一个远程调用,因此必然需要通过网络传输数据,且 RPC 常用于业务系统之间的数据交互,需要保证其可靠性,所以 RPC 一般默认采用 **TCP** 来传输。 +RPC 是一个远程调用,因此必然需要通过网络传输数据,且 RPC 常用于业务系统之间的数据交互,需要保证其可靠性,所以 RPC 一般默认采用 **TCP 协议**来传输。 网络传输数据是二进制数据,因此请求方需要将请求参数转为二进制数据,即**序列化**。 响应方接受到请求,要将二进制数据转换为请求参数,需要**反序列化**。 -请求方和响应方识别彼此的信息,需要约定好彼此数据的格式,即**协议**。大多数的协议会分成两部分,分别是数据头和消息体。数据头一般用于身份识别,包括协议标识、数据大小、请求类型、序列化类型等信息;消息体主要是请求的业务参数信息和扩展属性等。 +请求方和响应方识别彼此的信息,需要约定好彼此数据的格式,即**协议**。**大多数的协议会分成两部分,分别是数据头和消息体**。数据头一般用于身份识别,包括协议标识、数据大小、请求类型、序列化类型等信息;消息体主要是请求的业务参数信息和扩展属性等。 为了屏蔽底层通信细节,使用户聚焦自身业务,因此 RPC 框架一般引入了动态代理,通过依赖注入等技术,拦截方法调用,完成远程调用的通信逻辑。 @@ -54,23 +57,19 @@ RPC 是一个远程调用,因此必然需要通过网络传输数据,且 RPC RPC 框架能够帮助我们解决系统拆分后的通信问题,并且能让我们像调用本地一样去调用 远程方法。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619101023.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619101023.png) -## 协议 +## 协议:怎么设计可扩展且向后兼容的协议? ### 协议的作用 在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(合并的前提是同一个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP 窗口大小。对于服务提供方应用来说,他会从 TCP 通道里面收到很多的二进制数据,那这时候怎么识别出哪些二进制是第一个请求的呢? -这就好比让你读一篇没有标点符号的文章,你要怎么识别出每一句话到哪里结束呢?很简单啊,我们加上标点,完成断句就好了。 - -为了避免语义不一致的事情发生,我们就需要在发送请求的时候设定一个边界,然后在收到请求的时候按照这个设定的边界进行数据分割。这个边界语义的表达,就是我们所说的协议。 +个人理解:为了避免语义不一致的事情发生,需要为数据报文设定边界,请求方和接收方都按照设定的边界去读写数据。这类似于文章使用标点符号去断句。 ### 为何需要设计 RPC 协议 -有了现成的 HTTP 协议,为啥不直接用,还要为 RPC 设计私有协议呢? - -RPC 更多的是负责应用间的通信,所以性能要求相对更高。但 HTTP 协议的数据包大小相对请求数据本身要大很多,又需要加入很多无用的内容,比如换行符号、回车符等;还有一个更重要的原因是,HTTP 协议属于无状态协议,客户端无法对请求和响应进行关联,每次请求都需要重新建立连接,响应完成后再关闭连接。因此,对于要求高性能的 RPC 来说,HTTP 协议基本很难满足需求,所以 RPC 会选择设计更紧凑的私有协议。 +RPC 协议对性能要求高,而公有网络协议往往数据报文较大,内容不够紧凑。 ### 如何设计 RPC 协议? @@ -80,7 +79,7 @@ RPC 更多的是负责应用间的通信,所以性能要求相对更高。但 综上,一个 RPC 协议大概会由下图中的这些参数组成: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619102052.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619102052.png) ### 可扩展的协议 @@ -89,17 +88,19 @@ RPC 更多的是负责应用间的通信,所以性能要求相对更高。但 为了保证能平滑地升级改造前后的协议,我们有必要设计一种支持可扩展的协议。其关键在于让协议头支持可扩展,扩展后协议头的长度就不能定长了。那要实现读取不定长的协议头里面的内容,在这之前肯定需要一个固定的地方读取长度,所以我们需要一个固定的写入协议头的长度。整体协议就变成了三部分内容:固定部分、协议头内容、协议体内容。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619102833.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619102833.png) -## 序列化 +## 序列化:对象怎么在网络中传输? -在不同的场景下合理地选择序列化方式,对提升 RPC 框架整体的稳定性和性能是至关重要的。 +### 为什么需要序列化 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619101617.png) +调用方和被调用方的数据原本是对象,无法在网络中传输,必须转换为二进制数据。因此,需要一种方式来实现此过程:将对象转为二进制数据,即**序列化**;同时,需要根据二进制数据逆向转化为对象,即**反序列化**。 -序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。 +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619101617.png) -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619104420.png) +从 RPC 的实现角度来看,序列化的作用如下图所示: + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619104420.png) 常用序列化方式 @@ -110,26 +111,24 @@ RPC 更多的是负责应用间的通信,所以性能要求相对更高。但 - Protobuf - Thirft -**RPC 协议选型** +### RPC 协议选型 + +**优先级依次从高到低:安全性、通用性、兼容性、性能、效率、空间开销**。 -优先级依次从高到低:安全性、通用性、兼容性、性能、效率、空间开销。 +在序列化的选择上,与序列化协议的效率、性能、序列化协议后的体积相比,其通用性和兼容性的优先级会更高,因为他是会直接关系到服务调用的稳定性和可用率的,对于服务的性能来说,服务的可靠性显然更加重要。我们更加看重这种序列化协议在版本升级后的兼容性是否很好,是否支持更多的对象类型,是否是跨平台、跨语言的,是否有很多人已经用过并且踩过了很多的坑,其次我们才会去考虑性能、效率和空间开销。 -这归根结底还是因为服务调用的稳定性与可靠性,要比服务的性能与响应耗时更加重要。另 -外对于 RPC 调用来说,整体调用上,最为耗时、最消耗性能的操作大多都是服务提供者执 -行业务逻辑的操作,这时序列化的开销对于服务整体的开销来说影响相对较小。 +### 使用 RPC 需要注意哪些问题 -**使用 RPC 需要注意哪些问题** +- **对象构造得过于复杂** - 对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚; +- **对象过于复杂、庞大** - 入参对象与返回值对象体积不要太大,更不要传太大的集合; +- **使用序列化框架不支持的类作为入参类** - 尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类; +- **对象有复杂的继承关系** - 对象不要有复杂的继承关系,最好不要有父子类的情况。 -1. 对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚; -2. 入参对象与返回值对象体积不要太大,更不要传太大的集合; -3. 尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类; -4. 对象不要有复杂的继承关系,最好不要有父子类的情况。 +## 网络通信:RPC 框架在网络通信上更倾向于哪种网络 IO 模型? -## 网络通信 +### 常见的网络 IO 模型 -常见的网络 IO 模型分为四种:同步阻塞 IO(BIO)、同步非阻塞 IO(NIO)、IO 多路复 -用和异步非阻塞 IO(AIO)。在这四种 IO 模型中,只有 AIO 为异步 IO,其他都是同步 -IO。 +常见的网络 IO 模型分为四种:同步阻塞 IO(BIO)、同步非阻塞 IO(NIO)、IO 多路复用和异步非阻塞 IO(AIO)。在这四种 IO 模型中,只有 AIO 为异步 IO,其他都是同步 IO。 IO 多路复用(Reactor 模式)在高并发场景下使用最为广泛,很多知名软件都应用了这一技术,如:Netty、Redis、Nginx 等。 @@ -143,7 +142,7 @@ IO 多路复用分为 select,poll 和 epoll。 网络 IO 读写流程 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619174154.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619174154.png) 应用进程的每一次写操作,都会把数据写到用户空间的缓冲区中,再由 CPU 将数据拷贝到系统内核的缓冲区中,之后再由 DMA 将这份数据拷贝到网卡中,最后由网卡发送出去。这里我们可以看到,一次写操作数据要拷贝两次才能通过网卡发送出去,而用户进程的读操作则是将整个流程反过来,数据同样会拷贝两次才能让应用程序读取到数据。 @@ -151,7 +150,7 @@ IO 多路复用分为 select,poll 和 epoll。 所谓的零拷贝,就是取消用户空间与内核空间之间的数据拷贝操作,应用进程每一次的读写操作,可以通过一种方式,直接将数据写入内核或从内核中读取数据,再通过 DMA 将内核中的数据拷贝到网卡,或将网卡中的数据 copy 到内核。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619174335.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619174335.png) Netty 的零拷贝偏向于用户空间中对数据操作的优化,这对处理 TCP 传输中的拆包粘包问题有着重要的意义,对应用程序处理请求数据与返回数据也有重要的意义。 @@ -163,11 +162,11 @@ Netty 的 ByteBuffer 可以采用 Direct Buffers,使用堆外直接内存进 Netty 还提供 FileRegion 中包装 NIO 的 FileChannel.transferTo() 方法实现了零拷 贝,这与 Linux 中的 sendfile 方式在原理上也是一样的。 -## 动态代理 +## 动态代理:面向接口编程,屏蔽 RPC 处理流程 动态代理可以帮用户屏蔽远程调用的细节,实现像调用本地一样地调用远程的体验。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220619204255.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220619204255.png) JDK 支持的动态代理方式是通过实现 InvocationHandler 接口。这种方式有一定的局限性——它要求被代理的类只能是接口。原因是因为生成的代理类会继承 Proxy 类,但 Java 是不支持多重继承的。此外,它还有性能问题。它生成后的代理类是使用反射来完成方法调用的,而这种方式相对直接用编码调用来说,性能会降低。 @@ -177,11 +176,11 @@ Javassist 的是通过控制底层字节码来实现动态代理,不需要反 Byte Buddy 则属于后起之秀,在很多优秀的项目中,像 Spring、Jackson 都用到了 Byte Buddy 来完成底层代理。相比 Javassist,Byte Buddy 提供了更容易操作的 API,编写的代码可读性更高。更重要的是,生成的代理类执行速度比 Javassist 更快。 -## RPC 实战 +## RPC 实战:剖析 gRPC 源码,动手实现一个完整的 RPC 略 -## 架构设计 +## 架构设计:设计一个灵活的 RPC 框架 ### RPC 架构 @@ -197,7 +196,7 @@ RPC 还需要为调用方找到所有的服务提供方,并需要在 RPC 里 有了集群之后,提供方可能就需要管理好这些服务了,那我们的 RPC 就需要内置一些服务治理的功能,比如服务提供方权重的设置、调用授权等一些常规治理手段。而服务调用方需要额外做哪些事情呢?每次调用前,我们都需要根据服务提供方设置的规则,从集群中选择可用的连接用于发送请求。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220620112739.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220620112739.png) ### RPC 可扩展架构 @@ -205,13 +204,13 @@ RPC 还需要为调用方找到所有的服务提供方,并需要在 RPC 里 可以使用 SPI 技术来实现。注意:由于 JDK SPI 性能不高,并且不支持自动注入,所以,一般会选择其他的 SPI 实现。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220620113147.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220620113147.png) 有了 SPI 支持插件式加载后,RPC 框架就变成了一个微内核架构。 -## 服务发现 +## 服务发现:到底是要 CP 还是 AP? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220620144009.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220620144009.png) RPC 框架必须要有服务注册和发现机制,这样,集群中的节点才能知道通信方的请求地址。 @@ -224,7 +223,7 @@ RPC 框架必须要有服务注册和发现机制,这样,集群中的节点 搭建一个 ZooKeeper 集群作为注册中心集群,服务注册的时候只需要服务节点向 ZooKeeper 节点写入注册信息即可,利用 ZooKeeper 的 Watcher 机制完成服务订阅与服务下发功能 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200610180056.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200610180056.png) 通常我们可以使用 ZooKeeper、etcd 或者分布式缓存(如 Hazelcast)来解决事件通知问题,但当集群达到一定规模之后,依赖的 ZooKeeper 集群、etcd 集群可能就不稳定了,无法满足我们的需求。 @@ -242,23 +241,23 @@ ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的 而 RPC 框架的服务发现,在服务节点刚上线时,服务调用方是可以容忍在一段时间之后(比如几秒钟之后)发现这个新上线的节点的。毕竟服务节点刚上线之后的几秒内,甚至更长的一段时间内没有接收到请求流量,对整个服务集群是没有什么影响的,所以我们可以牺牲掉 CP(强制一致性),而选择 AP(最终一致),来换取整个注册中心集群的性能和稳定性。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717162006.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717162006.png) -## 健康检测 +## 健康检测:这个节点都挂了,为啥还要疯狂发请求? 健康检测,它能帮助我们从连接列表里面过滤掉一些存在问题的节点,避免在发请求的时候选择出有问题的节点而影响业务。 服务方状态一般有三种情况: -1. 健康状态:建立连接成功,并且心跳探活也一直成功; -2. 亚健康状态:建立连接成功,但是心跳请求连续失败; -3. 死亡状态:建立连接失败。 +1. **健康状态** - 建立连接成功,并且心跳探活也一直成功; +2. **亚健康状态** - 建立连接成功,但是心跳请求连续失败; +3. **死亡状态** - 建立连接失败。 设计健康检测方案的时候,不能简单地从 TCP 连接是否健康、心跳是否正常等简单维度考虑,因为健康检测的目的就是要保证“业务无损”,因此,可以加入业务请求可用率因素,这样能最大化地提升 RPC 接口可用率。 正常情况下,我们大概 30S 会发一次心跳请求,这个间隔一般不会太短,如果太短会给服务节点造成很大的压力。但是如果太长的话,又不能及时摘除有问题的节点。 -## 路由策略 +## 路由策略:怎么让请求按照设定的规则发到不同的节点上? 服务路由是指通过一定的规则从集群中选择合适的节点。 @@ -284,7 +283,7 @@ ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的 - 脚本路由:基于脚本语言的路由规则 - 标签路由:将服务分组的路由规则 -## 负载均衡 +## 负载均衡:节点负载差距这么大,为什么收到的流量还一样? ### 负载均衡算法 @@ -308,7 +307,7 @@ RPC 负载均衡所采用的策略与传统的 Web 服务负载均衡所采用 RPC 的负载均衡完全由 RPC 框架自身实现,RPC 的服务调用者会与“注册中心”下发的所有服务节点建立长连接,在每次发起 RPC 调用时,服务调用者都会通过配置的负载均衡插件,自主选择一个服务节点,发起 RPC 调用请求。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220622175324.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220622175324.png) ### 如何设计自适应的负载均衡 @@ -322,7 +321,7 @@ RPC 的负载均衡完全由 RPC 框架自身实现,RPC 的服务调用者会 健康值 = 指标值1 * 权重1 + 指标值2 * 权重2 + ... ``` -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220622180243.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220622180243.png) 服务调用者给每个服务节点都打完分之后,会发送请求,那这时候我们又该如何根据分数去控制给每个服务节点发送多少流量呢? @@ -334,7 +333,7 @@ RPC 的负载均衡完全由 RPC 框架自身实现,RPC 的服务调用者会 4. 可以配置开启哪些指标收集器,并设置这些参考指标的指标权重,再根据指标数据和指标权重来综合打分。 5. 通过服务节点的综合打分与节点的权重,最终计算出节点的最终权重,之后服务调用者会根据随机权重的策略,来选择服务节点。 -## 异常重试 +## 异常重试:在约定时间内安全可靠地重试 ### 异常重试 @@ -362,20 +361,20 @@ RPC 框架是不会知道哪些业务异常能够去进行异常重试的,我 综上,一个可靠的 RPC 容错处理机制如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200717163921.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200717163921.png) -## 优雅关闭 +## 优雅关闭:如何避免服务停机带来的业务损失? > 优雅关闭:如何避免服务停机带来的业务损失? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220623102847.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220623102847.png) 在服务重启的时候,对于调用方来说,可能会存在以下几种情况: - **调用方发请求前,目标服务已经下线**。对于调用方来说,跟目标节点的连接会断开,这时候调用方可以立马感知到,并且在其健康列表里面会把这个节点挪掉,自然也就不会被负载均衡选中。 - **调用方发请求的时候,目标服务正在关闭**。但调用方并不知道它正在关闭,而且两者之间的连接也没断开,所以这个节点还会存在健康列表里面,因此该节点就有一定概率会被负载均衡选中。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220623110010.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220623110010.png) 当服务提供方正在关闭,如果这之后还收到了新的业务请求,服务提供方直接返回一个特定的异常给调用方(比如 ShutdownException)。这个异常就是告诉调用方“我已经收到这个请求了,但是我正在关闭,并没有处理这个请求”,然后调用方收到这个异常响应后,RPC 框架把这个节点从健康列表挪出,并把请求自动重试到其他节点,因为这个请求是没有被服务提供方处理过,所以可以安全地重试到其他节点,这样就可以实现对业务无损。 @@ -383,7 +382,7 @@ RPC 框架是不会知道哪些业务异常能够去进行异常重试的,我 如何捕获到关闭事件呢?在 Java 语言里面,对应的是 Runtime.addShutdownHook 方法,可以注册关闭的钩子。在 RPC 启动的时候,我们提前注册关闭钩子,并在里面添加了两个处理程序,一个负责开启关闭标识,一个负责安全关闭服务对象,服务对象在关闭的时候会通知调用方下线节点。同时需要在我们调用链里面加上挡板处理器,当新的请求来的时候,会判断关闭标识,如果正在关闭,则抛出特定异常。 -## 优雅启动 +## 优雅启动:如何避免流量打到没有启动完成的节点? > 优雅启动:如何避免流量打到没有启动完成的节点? @@ -404,7 +403,7 @@ RPC 框架是不会知道哪些业务异常能够去进行异常重试的,我 最终的结果就是,调用方通过服务发现,除了可以拿到 IP 列表,还可以拿到对应的启动时间。我们需要把这个时间作用在负载均衡上。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220623114858.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220623114858.png) 通过这个小逻辑的改动,我们就可以保证当服务提供方运行时长小于预热时间时,对服务提供方进行降权,减少被负载均衡选择的概率,避免让应用在启动之初就处于高负载状态,从而实现服务提供方在启动后有一个预热的过程。 @@ -420,7 +419,7 @@ Bean 注册到 Spring-BeanFactory 里面去,而并不把这个 Bean 对应的 我们可以在服务提供方应用启动后,接口注册到注册中心前,预留一个 Hook 过程,让用户可以实现可扩展的 Hook 逻辑。用户可以在 Hook 里面模拟调用逻辑,从而使 JVM 指令能够预热起来,并且用户也可以在 Hook 里面事先预加载一些资源,只有等所有的资源都加载完成后,最后才把接口注册到注册中心。 -## 限流熔断 +## 熔断限流:业务如何实现自我保护 ### 限流 @@ -454,9 +453,9 @@ Hook 逻辑。用户可以在 Hook 里面模拟调用逻辑,从而使 JVM 指 熔断器的工作机制主要是关闭、打开和半打开这三个状态之间的切换。在正常情况下,熔断器是关闭的;当调用端调用下游服务出现异常时,熔断器会收集异常指标信息进行计算,当达到熔断条件时熔断器打开,这时调用端再发起请求是会直接被熔断器拦截,并快速地执行失败逻辑;当熔断器打开一段时间后,会转为半打开状态,这时熔断器允许调用端发送一个请求给服务端,如果这次请求能够正常地得到服务端的响应,则将状态置为关闭状态,否则 设置为打开。 -## 业务分组 +## 业务分组:如何隔离流量? -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200718204407.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200718204407.png) 在 RPC 里面我们可以通过分组的方式人为地给不同的调用方划分出不同的小集群,从而实现调用方流量隔离的效果,保障我们的核心业务不受非核心业务的干扰。但我们在考虑问题的时候,不能顾此失彼,不能因为新加一个的功能而影响到原有系统的稳定性。 @@ -468,7 +467,7 @@ Hook 逻辑。用户可以在 Hook 里面模拟调用逻辑,从而使 JVM 指 为了解决这种突发流量的问题,我们提供了一种更高效的方案,可以实现分组的快速伸缩。事实上我们还可以利用动态分组解决分组后给每个分组预留机器冗余的问题,我们没有必要把所有冗余的机器都分配到分组里面,我们可以把这些预留的机器做成一个共享的池子,从而减少整体预留的实例数量。 -## 异步 RPC +## 异步 RPC:压榨单机吞吐量 > 异步 RPC:压榨单机吞吐量 @@ -480,15 +479,15 @@ RPC 框架的异步策略主要是调用端异步与服务端异步。调用端 此外,RPC 框架也可以有其它的异步策略,比如集成 RxJava,再比如 gRPC 的 StreamObserver 入参对象,但 CompletableFuture 是 Java8 原生提供的,无代码入侵性,并且在使用上更加方便。 -## 安全体系 +## 安全体系:如何建立可靠的安全体系? RPC 是解决应用间互相通信的框架,而应用之间的远程调用过程一般不会暴露在公网,换句话讲就是说 RPC 一般用于解决内部应用之间的通信,而这个“内部”是指应用都部署在同一个大局域网内。相对于公网环境,局域网的隔离性更好,也就相对更安全,所以在 RPC 里面我们很少考虑像数据包篡改、请求伪造等恶意行为。 对于 RPC 来说,需要关心的安全问题不会有公网应用那么复杂,我们只要保证让服务调用方能拿到真实的服务提供方 IP 地址集合,且服务提供方可以管控调用自己的应用就够了(比如颁发数字签名)。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220623194151.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220623194151.png) -## 问题定位 +## 分布式环境下如何快速定位问题? > 问题定位:链路追踪 @@ -509,31 +508,21 @@ RPC 是解决应用间互相通信的框架,而应用之间的远程调用过 - [**SkyWalking**](https://skywalking.apache.org/):是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI 功能较强,接入端无代码侵入。目前已加入 Apache 孵化器。 - [**CAT**](https://github.com/dianping/cat):CAT 是美团点评开源的基于编码和配置的调用链分析,应用监控分析,日志采集,监控报警等一系列的监控平台工具。 -## 时钟轮 - -**定时任务带来了什么问题** - -无论是同步调用还是异步调用,调用端内部实行的都是异步,而调用端在向服务端发送消息之前会创建一个 Future,并存储这个消息标识与这个 Future 的映射,当服务端收到消息并且处理完毕后向调用端发送响应消 -息,调用端在接收到消息后会根据消息的唯一标识找到这个 Future,并将结果注入给这个 Future。 - -这个过程中,如果服务端没有及时响应消息给调用端呢?调用端该如何处理超时的请求? - -可以利用定时任务。每次创建一个 Future,我们都记录这个 Future 的创建时间与这个 Future 的超时时间,并且有一个定时任务进行检测,当这个 Future 到达超时时间并且没有被处理时,我们就对这个 Future 执行超时逻辑。 +## 详解时钟轮在 RPC 中的应用 -**如何实现定时任务** +无论是同步调用还是异步调用,调用端内部实行的都是异步,而调用端在向服务端发送消息之前会创建一个 Future,并存储这个消息标识与这个 Future 的映射,当服务端收到消息并且处理完毕后向调用端发送响应消息,调用端在接收到消息后会根据消息的唯一标识找到这个 Future,并将结果注入给这个 Future。 -方案一:每创建一个 Future 我们都启动一个线程,之后 sleep,到达超时时间就触发请求超时的处理逻辑。 +### 一般定时任务方案的缺点 -高并发场景下,单机可能每秒要发送数万次请求,请求超时时间设置的是 5 秒,那我们要创建多少个线程用来执行超时任务呢?超过 10 万个线程! +1. 方案一:每创建一个 Future 都启动一个线程,之后 sleep,到达超时时间就触发请求超时的处理逻辑。 -方案二:用一个线程来处理所有的定时任务。假设一个线程每隔 100 毫秒会扫描一遍所有的处理 Future 超时的任务,当发现一个 Future 超时了,我们就执行这个任务,对这个 Future 执行超时逻辑。 +- 缺点:需要创建大量线程。例如:高并发场景下,单机可能每秒要发送数万次请求,请求超时时间设置的是 5 秒,那我们要创建多少个线程用来执行超时任务呢?超过 10 万个线程! -高并发场景下,如果调用端刚好在 1 秒内发送了 1 万次请求,这 1 万次请求要在 5 秒后才会超时,那么那个扫 -描的线程在这个 5 秒内就会不停地对这 1 万个任务进行扫描遍历,要额外扫描 40 多次(每 100 毫秒扫描一次,5 秒内要扫描近 50 次),很浪费 CPU。 +2. 方案二:用一个线程来处理所有的定时任务,不断轮询定时任务。假设一个线程每隔 100 毫秒会扫描一遍所有的处理 Future 超时的任务,当发现一个 Future 超时了,我们就执行这个任务,对这个 Future 执行超时逻辑。 -**时钟轮** +- 缺点:很浪费 CPU。高并发场景下,如果调用端刚好在 1 秒内发送了 1 万次请求,这 1 万次请求要在 5 秒后才会超时,那么那个扫描的线程在这个 5 秒内就会不停地对这 1 万个任务进行扫描遍历,要额外扫描 40 多次(每 100 毫秒扫描一次,5 秒内要扫描近 50 次),很浪费 CPU。 -如何减少额外的扫描操作?比如一批定时任务是 5 秒之后执行,我在 4.9 秒之后才开始扫描这批定时任务,这样就大大地节省了 CPU。这时我们就可以利用时钟轮的机制了。 +### 时钟轮方案 在时钟轮机制中,有时间槽和时钟轮的概念,时间槽就相当于时钟的刻度,而时钟轮就相当于秒针与分针等跳动的一个周期,我们会将每个任务放到对应的时间槽位上。 @@ -546,7 +535,7 @@ RPC 是解决应用间互相通信的框架,而应用之间的远程调用过 - **调用端与服务端启动超时也可以应用到时钟轮**:以调用端为例,假设我们想要让应用可以快速地部署,例如 1 分钟内启动,如果超过 1 分钟则启动失败。我们可以在调用端启动时创建一个处理启动超时的定时任务,放到时钟轮里。 - **定时心跳**:RPC 框架调用端定时向服务端发送心跳,来维护连接状态,我们可以将心跳的逻辑封装为一个心跳任务,放到时钟轮里。 -## 流量回放 +## 流量回放:保障业务技术升级的神器 实际情况就是我们不仅要保障已有业务的稳定,还需要快速去完成各种新业务的需求,这期间我们的应用代码就会经常发生变化,而发生变化后就可能会引入新的不稳定因素,而且这个过程会一直持续不断发生。 @@ -554,19 +543,19 @@ RPC 是解决应用间互相通信的框架,而应用之间的远程调用过 应用引入了 RPC 后,所有的请求流量都会被 RPC 接管,所以我们可以很自然地在 RPC 里面支持流量回放功能。虽然这个功能本身并不是 RPC 的核心功能,但对于使用 RPC 的人来说,他们有了这个功能之后,就可以更放心地升级自己的应用了。 -## 动态分组 +## 动态分组:超高效实现秒级扩缩容 分组后带来的收益,它可以帮助服务提供方实现调用方的隔离。但是因为调用方流量并不是一成不变的,而且还可能会因为突发事件导致某个分组的流量溢出,而在整个大集群还有富余能力的时候,又因为分组隔离不能为出问题的集群提供帮助。 为了解决这种突发流量的问题,我们提供了一种更高效的方案,可以实现分组的快速扩缩容。事实上我们还可以利用动态分组解决分组后给每个分组预留机器冗余的问题,我们没有必要把所有冗余的机器都分配到分组里面,我们可以把这些预留的机器做成一个共享的池子,从而减少整体预留的实例数量。 -## 泛化调用 +## 如何在没有接口的情况下进行 RPC 调用? ### 应用场景 (1)**测试平台**:各个业务方在测试平台中通过输入接口、分组名、方法名以及参数值,在线测试自己发布的 RPC 服务。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220624110324.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220624110324.png) (2)**轻量级的服务网关**:可以让各个业务方用 HTTP 的方式,通过服务网关调用其它服务。服务网关要作为所有 RPC 服务的调用端,是不能依赖所有服务提供方的接口 API 的,也需要调用端在没有服务提供方提供接口的情况下,仍然可以正常地发起 RPC 调用。 @@ -574,7 +563,7 @@ RPC 是解决应用间互相通信的框架,而应用之间的远程调用过 所谓的 RPC 调用,本质上就是调用端向服务端发送一条请求消息,服务端接收并处理,之后向调用端发送一条响应消息,调用端处理完响应消息之后,一次 RPC 调用就完成了。只要调用端将服务端需要知道的信息,如接口名、业务分组名、方法名以及参数信息等封装成请求消息发送给服务端,服务端就能够解析并处理这条请求消息,这样问题就解决了。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220624110611.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220624110611.png) 泛化调用接口示例: @@ -585,7 +574,7 @@ class GenericService { } ``` -## 兼容多种 RPC 协议 +## 如何在线上环境里兼容多种 RPC 协议? 业界有很多 RPC 框架,如:Dubbo、Hessian、gRPC 等,它们随着技术发展逐渐涌现出来。不同时期、不同项目为了解决自身的通信问题,可能会选择不同的 RPC 框架。 @@ -595,7 +584,7 @@ class GenericService { 在保持原有 RPC 使用方式不变的情况下,同时引入新的 RPC 框架的思路,是可以让所有的应用最终都能升级到我们想要升级的 RPC 上,但对于开发人员来说,这样切换成本还是有点儿高,整个过程最少需要两次上线才能彻底地把应用里面的旧 RPC 都切换成新 RPC。还有一种方案:要让新的 RPC 能同时支持多种 RPC 调用,当一个调用方切换到新的 RPC 之后,调用方和服务提供方之间就可以用新的协议完成调用;当调用方还是用老的 RPC 进行调用的话,调用方和服务提供方之间就继续沿用老的协议完成调用。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220624112147.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220624112147.png) ### 如何优雅处理多协议 @@ -607,8 +596,8 @@ class GenericService { 当完成了真正的方法调用以后,RPC 返回的也是一个跟协议无关的通用对象,所以在真正往调用方写回数据的时候,我们同样需要完成一个对象转换的逻辑,只不过这时候是把通用对象转成协议相关的对象。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220624112208.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220624112208.png) ## 参考资料 -- [《RPC 实战与核心原理》](https://time.geekbang.org/column/intro/280) \ No newline at end of file +- [RPC 实战与核心原理](https://time.geekbang.org/column/intro/100046201) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.Dubbo\346\272\220\347\240\201\350\247\243\350\257\273\344\270\216\345\256\236\346\210\230\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.Dubbo\346\272\220\347\240\201\350\247\243\350\257\273\344\270\216\345\256\236\346\210\230\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..50c1131f8e --- /dev/null +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/02.Dubbo\346\272\220\347\240\201\350\247\243\350\257\273\344\270\216\345\256\236\346\210\230\347\254\224\350\256\260.md" @@ -0,0 +1,358 @@ +--- +title: 《Dubbo 源码解读与实战》笔记 +date: 2023-06-25 19:24:38 +order: 02 +categories: + - 笔记 + - 分布式 + - 分布式通信 +tags: + - 分布式 + - 分布式通信 + - RPC + - Dubbo +permalink: /pages/10b5b8/ +--- + +# 《Dubbo 源码解读与实战》笔记 + +## 开篇词 深入掌握 Dubbo 原理与实现,提升你的职场竞争力 + +[Apache Dubbo](http://dubbo.apache.org/zh-cn/)是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力: + +- 面向接口的远程方法调用; +- 可靠、智能的容错和负载均衡; +- 服务自动注册和发现能力。 + +**Dubbo 是一个分布式服务框架,致力于提供高性能、透明化的 RPC 远程服务调用方案以及服务治理方案,以帮助我们解决微服务架构落地时的问题。** + +## Dubbo 源码环境搭建:千里之行,始于足下 + +### Dubbo 核心组件 + +![](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/distributed/rpc/dubbo/dubbo基本架构.png) + +Registry - **注册中心**。负责服务地址的注册与查找,服务的 Provider 和 Consumer 只在启动时与注册中心交互。注册中心通过长连接感知 Provider 的存在,在 Provider 出现宕机的时候,注册中心会立即推送相关事件通知 Consumer。 + +Provider - **服务提供者**。在它启动的时候,会向 Registry 进行注册操作,将自己服务的地址和相关配置信息封装成 URL 添加到 ZooKeeper 中。 + +Consumer - **服务消费者**。在它启动的时候,会向 Registry 进行订阅操作。订阅操作会从 ZooKeeper 中获取 Provider 注册的 URL,并在 ZooKeeper 中添加相应的监听器。获取到 Provider URL 之后,Consumer 会根据负载均衡算法从多个 Provider 中选择一个 Provider 并与其建立连接,最后发起对 Provider 的 RPC 调用。 如果 Provider URL 发生变更,Consumer 将会通过之前订阅过程中在注册中心添加的监听器,获取到最新的 Provider URL 信息,进行相应的调整,比如断开与宕机 Provider 的连接,并与新的 Provider 建立连接。Consumer 与 Provider 建立的是长连接,且 Consumer 会缓存 Provider 信息,所以一旦连接建立,即使注册中心宕机,也不会影响已运行的 Provider 和 Consumer。 + +Monitor - **监控中心**。用于统计服务的调用次数和调用时间。Provider 和 Consumer 在运行过程中,会在内存中统计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。监控中心在上面的架构图中并不是必要角色,监控中心宕机不会影响 Provider、Consumer 以及 Registry 的功能,只会丢失监控数据而已。 + +Container - 服务运行容器。 + +### Dubbo 核心模块 + +- **dubbo-common 模块:** Dubbo 的一个公共模块,其中有很多工具类以及公共逻辑,如 Dubbo SPI 实现、时间轮实现、动态编译器等。 +- **dubbo-remoting 模块:** Dubbo 的远程通信模块,其中的子模块依赖各种开源组件实现远程通信。在 dubbo-remoting-api 子模块中定义该模块的抽象概念,在其他子模块中依赖其他开源组件进行实现,例如,dubbo-remoting-netty4 子模块依赖 Netty 4 实现远程通信,dubbo-remoting-zookeeper 通过 Apache Curator 实现与 ZooKeeper 集群的交互。 +- **dubbo-rpc 模块:** Dubbo 中对远程调用协议进行抽象的模块,其中抽象了各种协议,依赖于 dubbo-remoting 模块的远程调用功能。dubbo-rpc-api 子模块是核心抽象,其他子模块是针对具体协议的实现,例如,dubbo-rpc-dubbo 子模块是对 Dubbo 协议的实现,依赖了 dubbo-remoting-netty4 等 dubbo-remoting 子模块。 dubbo-rpc 模块的实现中只包含一对一的调用,不关心集群的相关内容。 +- **dubbo-cluster 模块:** Dubbo 中负责管理集群的模块,提供了负载均衡、容错、路由等一系列集群相关的功能,最终的目的是将多个 Provider 伪装为一个 Provider,这样 Consumer 就可以像调用一个 Provider 那样调用 Provider 集群了。 +- **dubbo-registry 模块:** Dubbo 中负责与多种开源注册中心进行交互的模块,提供注册中心的能力。其中, dubbo-registry-api 子模块是顶层抽象,其他子模块是针对具体开源注册中心组件的具体实现,例如,dubbo-registry-zookeeper 子模块是 Dubbo 接入 ZooKeeper 的具体实现。 +- **dubbo-monitor 模块:** Dubbo 的监控模块,主要用于统计服务调用次数、调用时间以及实现调用链跟踪的服务。 +- **dubbo-config 模块:** Dubbo 对外暴露的配置都是由该模块进行解析的。例如,dubbo-config-api 子模块负责处理 API 方式使用时的相关配置,dubbo-config-spring 子模块负责处理与 Spring 集成使用时的相关配置方式。有了 dubbo-config 模块,用户只需要了解 Dubbo 配置的规则即可,无须了解 Dubbo 内部的细节。 +- **dubbo-metadata 模块:** Dubbo 的元数据模块。dubbo-metadata 模块的实现套路也是有一个 api 子模块进行抽象,然后其他子模块进行具体实现。 +- **dubbo-configcenter 模块:** Dubbo 的动态配置模块,主要负责外部化配置以及服务治理规则的存储与通知,提供了多个子模块用来接入多种开源的服务发现组件。 + +## Dubbo 的配置总线:抓住 URL,就理解了半个 Dubbo + +Dubbo 中任意的一个实现都可以抽象为一个 URL,Dubbo 使用 URL 来统一描述了所有对象和配置信息,并贯穿在整个 Dubbo 框架之中。Dubbo URL 格式如下: + +``` +protocol://username:password@host:port/path?key=value&key=value +``` + +- **protocol**:URL 的协议。我们常见的就是 HTTP 协议和 HTTPS 协议,当然,还有其他协议,如 FTP 协议、SMTP 协议等。 +- **username/password**:用户名/密码。 HTTP Basic Authentication 中多会使用在 URL 的协议之后直接携带用户名和密码的方式。 +- **host/port**:主机/端口。在实践中一般会使用域名,而不是使用具体的 host 和 port。 +- **path**:请求的路径。 +- **parameters**:参数键值对。一般在 GET 请求中会将参数放到 URL 中,POST 请求会将参数放到请求体中。 + +Dubbo 中和 URL 相关的核心类: + +- **URL** - 定义了 URL 的结构; +- **URLBuilder,** 辅助构造 URL; +- **URLStrParser,** 将字符串解析成 URL 对象。 + +### Dubbo 中的 URL 示例 + +URL 在 SPI 中的应用:RegistryFactory.getRegistry() 方法。 + +URL 在服务暴露中的应用:ZookeeperRegistry.doRegister() 方法。 + +URL 在服务订阅中的应用:Registry.doSubscribe() 方法 + +## Dubbo SPI 精析,接口实现两极反转(上) + +Dubbo 通过 SPI 机制来实现微内核架构,以达到 OCP 原则(即“对扩展开放,对修改封闭”的原则)。 + +JDK SPI 要点: + +- 在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件 +- 此文件记录了该 jar 包提供的服务接口的具体实现类 + +JDK SPI 源码分析 + +ServiceLoader.load() 方法,首先会尝试获取当前使用的 ClassLoader;查找失败后使用 SystemClassLoader;然后调用 reload() 方法。 + +在 reload() 方法中,首先会清理 providers 缓存(LinkedHashMap 类型的集合),该缓存用来记录 ServiceLoader 创建的实现对象,其中 Key 为实现类的完整类名,Value 为实现类的对象。之后创建 LazyIterator 迭代器,用于读取 SPI 配置文件并实例化实现类对象。 + +## Dubbo SPI 精析,接口实现两极反转(下) + +Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。 + +- META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。 +- META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。 +- META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。 + +Dubbo 将 SPI 配置文件改成了 **KV 格式**,例如: + +```ini +dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol +``` + +### SPI 核心实现 + +@SPI 注解 + +Dubbo SPI 的核心逻辑几乎都封装在 ExtensionLoader 之中。 + +ExtensionLoader 中三个核心的静态字段。 + +- **strategies(LoadingStrategy[]类型):** LoadingStrategy 接口有三个实现(通过 JDK SPI 方式加载的),分别对应前面介绍的三个 Dubbo SPI 配置文件所在的目录 +- **EXTENSION_LOADERS(ConcurrentMap类型)** :Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例,该集合缓存了全部 ExtensionLoader 实例,其中的 Key 为扩展接口,Value 为加载其扩展实现的 ExtensionLoader 实例。 +- **EXTENSION_INSTANCES(ConcurrentMap, Object>类型)**:该集合缓存了扩展实现类与其实例对象的映射关系。在前文示例中,Key 为 Class,Value 为 DubboProtocol 对象。 + +## 海量定时任务,一个时间轮搞定 + +**时间轮是一种高效的、批量管理定时任务的调度模型**。时间轮一般会实现成一个环形结构,类似一个时钟,分为很多槽,一个槽代表一个时间间隔,每个槽使用双向链表存储定时任务;指针周期性地跳动,跳动到一个槽位,就执行该槽位的定时任务。 + +需要注意的是,单层时间轮的容量和精度都是有限的,对于精度要求特别高、时间跨度特别大或是海量定时任务需要调度的场景,通常会使用**多级时间轮**以及**持久化存储与时间轮结合**的方案。 + +核心接口和类: + +- TimerTask 接口 +- Timer 接口 +- Timeout 接口 +- HashedWheelTimeout 类 +- HashedWheelBucket 类 +- HashedWheelTimer 类 + +## ZooKeeper 与 Curator,求你别用 ZkClient 了(上) + +Dubbo 目前支持 Consul、etcd、Nacos、ZooKeeper、Redis 等多种开源组件作为注册中心,并且在 Dubbo 源码也有相应的接入模块。 + +**ZooKeeper 是一个针对分布式系统的、可靠的、可扩展的协调服务**,它通常作为统一命名服务、统一配置管理、注册中心(分布式集群管理)、分布式锁服务、Leader 选举服务等角色出现。 + +ZooKeeper 集群中的角色 + +- **Client 节点**:从业务角度来看,这是分布式应用中的一个节点,通过 ZkClient 或是其他 ZooKeeper 客户端与 ZooKeeper 集群中的一个 Server 实例维持长连接,并定时发送心跳。从 ZooKeeper 集群的角度来看,它是 ZooKeeper 集群的一个客户端,可以主动查询或操作 ZooKeeper 集群中的数据,也可以在某些 ZooKeeper 节点(ZNode)上添加监听。当被监听的 ZNode 节点发生变化时,例如,该 ZNode 节点被删除、新增子节点或是其中数据被修改等,ZooKeeper 集群都会立即通过长连接通知 Client。 +- **Leader 节点**:ZooKeeper 集群的主节点,负责整个 ZooKeeper 集群的写操作,保证集群内事务处理的顺序性。同时,还要负责整个集群中所有 Follower 节点与 Observer 节点的数据同步。 +- **Follower 节点**:ZooKeeper 集群中的从节点,可以接收 Client 读请求并向 Client 返回结果,并不处理写请求,而是转发到 Leader 节点完成写入操作。另外,Follower 节点还会参与 Leader 节点的选举。 +- **Observer 节点**:ZooKeeper 集群中特殊的从节点,不会参与 Leader 节点的选举,其他功能与 Follower 节点相同。引入 Observer 角色的目的是增加 ZooKeeper 集群读操作的吞吐量,如果单纯依靠增加 Follower 节点来提高 ZooKeeper 的读吞吐量,那么有一个很严重的副作用,就是 ZooKeeper 集群的写能力会大大降低,因为 ZooKeeper 写数据时需要 Leader 将写操作同步给半数以上的 Follower 节点。引入 Observer 节点使得 ZooKeeper 集群在写能力不降低的情况下,大大提升了读操作的吞吐量。 + +ZNode 节点类型有如下四种: + +- **持久节点。** 持久节点创建后,会一直存在,不会因创建该节点的 Client 会话失效而删除。 +- **持久顺序节点。** 持久顺序节点的基本特性与持久节点一致,创建节点的过程中,ZooKeeper 会在其名字后自动追加一个单调增长的数字后缀,作为新的节点名。 +- **临时节点。** 创建临时节点的 ZooKeeper Client 会话失效之后,其创建的临时节点会被 ZooKeeper 集群自动删除。与持久节点的另一点区别是,临时节点下面不能再创建子节点。 +- **临时顺序节点。** 基本特性与临时节点一致,创建节点的过程中,ZooKeeper 会在其名字后自动追加一个单调增长的数字后缀,作为新的节点名。 + +## ZooKeeper 与 Curator,求你别用 ZkClient 了(下) + +## 代理模式与常见实现 + +代理模式 + +### JDK 动态代理 + +JDK 动态代理的核心是 InvocationHandler 接口。 + +### CGLIB + +CGLib(Code Generation Library)是一个基于 ASM 的字节码生成库。它允许我们在运行时对字节码进行修改和动态生成。CGLib 采用字节码技术实现动态代理功能,其底层原理是通过字节码技术为目标类生成一个子类,并在该子类中采用方法拦截的方式拦截所有父类方法的调用,从而实现代理的功能。 + +因为 CGLib 使用生成子类的方式实现动态代理,所以无法代理 final 关键字修饰的方法(因为 final 方法是不能够被重写的)。这样的话,**CGLib 与 JDK 动态代理之间可以相互补充:在目标类实现接口时,使用 JDK 动态代理创建代理对象;当目标类没有实现接口时,使用 CGLib 实现动态代理的功能**。在 Spring、MyBatis 等多种开源框架中,都可以看到 JDK 动态代理与 CGLib 结合使用的场景。 + +CGLib 的实现有两个重要的成员组成。 + +- **Enhancer**:指定要代理的目标对象以及实际处理代理逻辑的对象,最终通过调用 create() 方法得到代理对象,对这个对象所有的非 final 方法的调用都会转发给 MethodInterceptor 进行处理。 +- **MethodInterceptor**:动态代理对象的方法调用都会转发到 intercept 方法进行增强。 + +### Javassist + +**Javassist 是一个开源的生成 Java 字节码的类库**,其主要优点在于简单、快速,直接使用 Javassist 提供的 Java API 就能动态修改类的结构,或是动态生成类。 + +## Netty 入门,用它做网络编程都说好(上) + +### Netty I/O 模型设计 + +#### 传统阻塞 I/O 模型 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626221005.png) + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626221044.png) + +#### I/O 多路复用模型 + +针对传统的阻塞 I/O 模型的缺点,I/O 复用的模型在性能方面有不小的提升。I/O 复用模型中的多个连接会共用一个 Selector 对象,由 Selector 感知连接的读写事件,而此时的线程数并不需要和连接数一致,只需要很少的线程定期从 Selector 上查询连接的读写状态即可,无须大量线程阻塞等待连接。当某个连接有新的数据可以处理时,操作系统会通知线程,线程从阻塞状态返回,开始进行读写操作以及后续的业务逻辑处理。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626221405.png) + +Netty 就是采用了上述 I/O 复用的模型。由于多路复用器 Selector 的存在,可以同时并发处理成百上千个网络连接,大大增加了服务器的处理能力。另外,Selector 并不会阻塞线程,也就是说当一个连接不可读或不可写的时候,线程可以去处理其他可读或可写的连接,这就充分提升了 I/O 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程切换。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626221543.png) + +### Netty 线程模型设计 + +**Netty 采用了 Reactor 线程模型的设计。** Reactor 模式,也被称为 Dispatcher 模式,**核心原理是 Selector 负责监听 I/O 事件,在监听到 I/O 事件之后,分发(Dispatch)给相关线程进行处理**。 + +#### 单 Reactor 单线程 + +Reactor 对象监听客户端请求事件,收到事件后通过 Dispatch 进行分发。如果是连接建立的事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接建立之后的业务请求。如果不是连接建立的事件,而是数据的读写事件,则 Reactor 会将事件分发对应的 Handler 来处理,由这里唯一的线程调用 Handler 对象来完成读取数据、业务处理、发送响应的完整流程。当然,该过程中也可能会出现连接不可读或不可写等情况,该单线程会去执行其他 Handler 的逻辑,而不是阻塞等待。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626221855.png) + +单 Reactor 单线程的优点就是:线程模型简单,没有引入多线程,自然也就没有多线程并发和竞争的问题。 + +但其缺点也非常明显,那就是**性能瓶颈问题**,一个线程只能跑在一个 CPU 上,能处理的连接数是有限的,无法完全发挥多核 CPU 的优势。一旦某个业务逻辑耗时较长,这唯一的线程就会卡在上面,无法处理其他连接的请求,程序进入假死的状态,可用性也就降低了。正是由于这种限制,一般只会在**客户端**使用这种线程模型。 + +#### 单 Reactor 多线程 + +在单 Reactor 多线程的架构中,Reactor 监控到客户端请求之后,如果连接建立的请求,则由 Acceptor 通过 accept 处理,然后创建一个 Handler 对象处理连接建立之后的业务请求。如果不是连接建立请求,则 Reactor 会将事件分发给调用连接对应的 Handler 来处理。到此为止,该流程与单 Reactor 单线程的模型基本一致,**唯一的区别就是执行 Handler 逻辑的线程隶属于一个线程池**。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626222342.png) + +很明显,单 Reactor 多线程的模型可以充分利用多核 CPU 的处理能力,提高整个系统的吞吐量,但引入多线程模型就要考虑线程并发、数据共享、线程调度等问题。在这个模型中,只有一个线程来处理 Reactor 监听到的所有 I/O 事件,其中就包括连接建立事件以及读写事件,当连接数不断增大的时候,这个唯一的 Reactor 线程也会遇到瓶颈。 + +#### 主从 Reactor 多线程 + +为了解决单 Reactor 多线程模型中的问题,我们可以引入多个 Reactor。其中,Reactor 主线程负责通过 Acceptor 对象处理 MainReactor 监听到的连接建立事件,当 Acceptor 完成网络连接的建立之后,MainReactor 会将建立好的连接分配给 SubReactor 进行后续监听。 + +当一个连接被分配到一个 SubReactor 之上时,会由 SubReactor 负责监听该连接上的读写事件。当有新的读事件(OP_READ)发生时,Reactor 子线程就会调用对应的 Handler 读取数据,然后分发给 Worker 线程池中的线程进行处理并返回结果。待处理结束之后,Handler 会根据处理结果调用 send 将响应返回给客户端,当然此时连接要有可写事件(OP_WRITE)才能发送数据。 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626222425.png) + +主从 Reactor 多线程的设计模式解决了单一 Reactor 的瓶颈。**主从 Reactor 职责明确,主 Reactor 只负责监听连接建立事件,SubReactor 只负责监听读写事件**。整个主从 Reactor 多线程架构充分利用了多核 CPU 的优势,可以支持扩展,而且与具体的业务逻辑充分解耦,复用性高。但不足的地方是,在交互上略显复杂,需要一定的编程门槛。 + +#### Netty 线程模型 + +Netty 同时支持上述几种线程模式 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626222709.png) + +#### + +**Netty 抽象出两组线程池:BossGroup 专门用于接收客户端的连接,WorkerGroup 专门用于网络的读写**。BossGroup 和 WorkerGroup 类型都是 NioEventLoopGroup,相当于一个事件循环组,其中包含多个事件循环 ,每一个事件循环是 NioEventLoop。 + +NioEventLoop 表示一个不断循环的、执行处理任务的线程,每个 NioEventLoop 都有一个 Selector 对象与之对应,用于监听绑定在其上的连接,这些连接上的事件由 Selector 对应的这条线程处理。每个 NioEventLoopGroup 可以含有多个 NioEventLoop,也就是多个线程。 + +每个 Boss NioEventLoop 会监听 Selector 上连接建立的 accept 事件,然后处理 accept 事件与客户端建立网络连接,生成相应的 NioSocketChannel 对象,一个 NioSocketChannel 就表示一条网络连接。之后会将 NioSocketChannel 注册到某个 Worker NioEventLoop 上的 Selector 中。 + +每个 Worker NioEventLoop 会监听对应 Selector 上的 read/write 事件,当监听到 read/write 事件的时候,会通过 Pipeline 进行处理。一个 Pipeline 与一个 Channel 绑定,在 Pipeline 上可以添加多个 ChannelHandler,每个 ChannelHandler 中都可以包含一定的逻辑,例如编解码等。Pipeline 在处理请求的时候,会按照我们指定的顺序调用 ChannelHandler。 + +## Netty 入门,用它做网络编程都说好(下) + +### Channel + +Channel 是 Netty 对网络连接的抽象,核心功能是执行网络 I/O 操作。不同协议、不同阻塞类型的连接对应不同的 Channel 类型。 + +常用的 NIO Channel 类型。 + +- **NioSocketChannel**:对应异步的 TCP Socket 连接。 +- **NioServerSocketChannel**:对应异步的服务器端 TCP Socket 连接。 +- **NioDatagramChannel**:对应异步的 UDP 连接。 + +### ChannelFuture + +### Selector + +**Selector 是对多路复用器的抽象**,也是 Java NIO 的核心基础组件之一。Netty 就是基于 Selector 对象实现 I/O 多路复用的,在 Selector 内部,会通过系统调用不断地查询这些注册在其上的 Channel 是否有已就绪的 I/O 事件,例如,可读事件(OP_READ)、可写事件(OP_WRITE)或是网络连接事件(OP_ACCEPT)等,而无须使用用户线程进行轮询。这样,我们就可以用一个线程监听多个 Channel 上发生的事件。 + +EventLoop + +EventLoopGroup + +## 简易版 RPC 框架实现(上) + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230626223647.png) + +## 简易版 RPC 框架实现(下) + +## 本地缓存:降低 ZooKeeper 压力的一个常用手段 + +## 重试机制是网络操作的基本保证 + +## ZooKeeper 注册中心实现,官方推荐注册中心实践 + +## Dubbo Serialize 层:多种序列化算法,总有一款适合你 + +## Dubbo Remoting 层核心接口分析:这居然是一套兼容所有 NIO 框架的设计? + +## Buffer 缓冲区:我们不生产数据,我们只是数据的搬运工 + +## Transporter 层核心实现:编解码与线程模型一文打尽(上) + +## Transporter 层核心实现:编解码与线程模型一文打尽(下) + +## Exchange 层剖析:彻底搞懂 Request-Response 模型(上) + +## Exchange 层剖析:彻底搞懂 Request-Response 模型(下) + +## 核心接口介绍,RPC 层骨架梳理 + +## 从 Protocol 起手,看服务暴露和服务引用的全流程(上) + +## 从 Protocol 起手,看服务暴露和服务引用的全流程(下) + +## 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker(上) + +## 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker(下) + +## 复杂问题简单化,代理帮你隐藏了多少底层细节? + +## 加餐:HTTP 协议 + JSON-RPC,Dubbo 跨语言就是如此简单 + +## Filter 接口,扩展 Dubbo 框架的常用手段指北 + +## 加餐:深潜 Directory 实现,探秘服务目录玄机 + +## 路由机制:请求到底怎么走,它说了算(上) + +## 路由机制:请求到底怎么走,它说了算(下) + +## 加餐:初探 Dubbo 动态配置的那些事儿 + +## 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(上) + +## 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下) + +## 集群容错:一个好汉三个帮(上) + +## 集群容错:一个好汉三个帮(下) + +## 加餐:多个返回值不用怕,Merger 合并器来帮忙 + +## 加餐:模拟远程调用,Mock 机制帮你搞定 + +## 加餐:一键通关服务发布全流程 + +## 加餐:服务引用流程全解析 + +## 服务自省设计方案:新版本新方案 + +## 元数据方案深度剖析,如何避免注册中心数据量膨胀? + +## 加餐:深入服务自省方案中的服务发布订阅(上) + +## 加餐:深入服务自省方案中的服务发布订阅(下) + +## 配置中心设计与实现:集中化配置 and 本地化配置,我都要(上) + +## 配置中心设计与实现:集中化配置 and 本地化配置,我都要(下) + +## 结束语 认真学习,缩小差距 + +## 参考资料 + +- [《Dubbo 源码解读与实战》](https://kaiwu.lagou.com/course/courseInfo.htm?courseId=393) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/11.\346\266\210\346\201\257\351\230\237\345\210\227\351\253\230\346\211\213\350\257\276\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/11.\346\266\210\346\201\257\351\230\237\345\210\227\351\253\230\346\211\213\350\257\276\347\254\224\350\256\260.md" index 949711469e..52196d7e02 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/11.\346\266\210\346\201\257\351\230\237\345\210\227\351\253\230\346\211\213\350\257\276\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/11.\346\266\210\346\201\257\351\230\237\345\210\227\351\253\230\346\211\213\350\257\276\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《消息队列高手课》笔记 date: 2022-05-11 20:59:25 +order: 11 categories: - 笔记 - 分布式 @@ -83,7 +84,7 @@ RabbitMQ,它是少数依然坚持使用队列模型的产品之一。那它是 在 RabbitMQ 中,Exchange 位于生产者和队列之间,生产者并不关心将消息发送给哪个队列,而是将消息发送给 Exchange,由 Exchange 上配置的策略来决定将消息投递到哪些队列中。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220511211021.jfif) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220511211021.jfif) 同一份消息如果需要被多个消费者来消费,需要配置 Exchange 将消息发送到多个队列,每个队列中都存放一份完整的消息数据,可以为一个消费者提供消费服务。这也可以变相地实现新发布 - 订阅模型中,“一份消息数据可以被多个订阅者来多次消费”这样的功能。 @@ -124,7 +125,7 @@ RocketMQ 中,订阅者的概念是通过消费组(Consumer Group)来体现 **正常情况——事务主动方发消息** 这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220512194221.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220512194221.png) 1. 发送方向 MQ 服务端(MQ Server)发送 half 消息。 2. MQ Server 将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功。 @@ -134,7 +135,7 @@ RocketMQ 中,订阅者的概念是通过消费组(Consumer Group)来体现 **异常情况——事务主动方消息恢复** 在断网或者应用重启等异常情况下,图中 4 提交的二次确认超时未到达 MQ Server,此时处理逻辑如下: -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220512194230.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20220512194230.png) 5. MQ Server 对该消息发起消息回查。 6. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。 diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/13.Kafka\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\350\257\273\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/13.Kafka\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\350\257\273\347\254\224\350\256\260.md" index 8b0ab1f1d6..60bab6d270 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/13.Kafka\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\350\257\273\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/13.Kafka\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\350\257\273\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《Kafka 核心源码解读》笔记 date: 2022-07-03 14:53:05 +order: 13 categories: - 笔记 - 分布式 @@ -17,7 +18,7 @@ permalink: /pages/f5f5ef/ ## 开篇词 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703152740.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703152740.png) 从功能上讲,Kafka 源码分为四大模块。 @@ -26,7 +27,7 @@ permalink: /pages/f5f5ef/ - Connect 源码:用于实现 Kafka 与外部系统的高性能数据传输。 - Streams 源码:用于实现实时的流处理功能。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220703152803.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220703152803.png) ## 导读 @@ -47,7 +48,7 @@ kafka 项目主要目录结构 ### Kafka 日志结构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220704204019.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220704204019.png) Kafka 日志对象由多个日志段对象组成,而每个日志段对象会在磁盘上创建一组文件,包括**消息日志文件(.log)**、**位移索引文件(.index)**、**时间戳索引文件(.timeindex)**以及已中止(Aborted)事务的**索引文件(.txnindex)**。当然,如果你没有使用 Kafka 事务,已中止事务的索引文件是不会被创建出来的。 @@ -94,7 +95,7 @@ append 方法接收 4 个参数:分别表示 - `shallowOffsetOfMaxTimestamp`:最大时间戳对应消息的位移 - `records`:真正要写入的消息集合 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220705062643.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220705062643.png) - 第一步:在源码中,首先调用 `log.sizeInBytes` 方法判断该日志段是否为空,如果是空的话, Kafka 需要记录要写入消息集合的最大时间戳,并将其作为后面新增日志段倒计时的依据。 - 第二步:代码调用 `ensureOffsetInRange` 方法确保输入参数最大位移值是合法的。那怎么判断是不是合法呢?标准就是看它与日志段起始位移的差值是否在整数范围内,即 `largestOffset - baseOffset` 的值是不是 介于 `[0,Int.MAXVALUE]` 之间。在极个别的情况下,这个差值可能会越界,这时, `append` 方法就会抛出异常,阻止后续的消息写入。一旦你碰到这个问题,你需要做的是升级你的 Kafka 版本,因为这是由已知的 Bug 导致的。 @@ -121,7 +122,7 @@ read 方法代码逻辑: #### recover 方法 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220705064515.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220705064515.png) recover 开始时,代码依次调用索引对象的 reset 方法清空所有的索引文件,之后会开始遍历日志段中的所有消息集合或消息批次(RecordBatch)。对于读取到的每个消息集合,日志段必须要确保它们是合法的,这主要体现在两个方面: @@ -136,7 +137,7 @@ recover 开始时,代码依次调用索引对象的 reset 方法清空所有 日志是日志段的容器,里面定义了很多管理日志段的操作。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220705195916.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220705195916.png) ### Log 源码结构 @@ -197,7 +198,7 @@ class LocalLog(@volatile private var _dir: File, Log End Offset(LEO),是表示日志下一条待插入消息的位移值,而这个 Log Start Offset 是跟它相反的,它表示日志当前对外可见的最早一条消息的位移值。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220705201758.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220705201758.png) 图中绿色的位移值 3 是日志的 Log Start Offset,而位移值 15 表示 LEO。另外,位移值 8 是高水位值,它是区分已提交消息和未提交消息的分水岭。 @@ -214,7 +215,7 @@ Log 类的其他属性你暂时不用理会,因为它们要么是很明显的 ### LOG 类初始化逻辑 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220705204919.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220705204919.png) ### Log 的常见操作 @@ -399,7 +400,7 @@ Log Start Offset 被更新的时机: 写操作流程: -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220706104752.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220706104752.png) 读操作 @@ -420,7 +421,7 @@ read 方法中有 4 个参数: - TimeIndex.scala:定义时间戳索引,保存“< 时间戳,位移值 >”对。 - TransactionIndex.scala:定义事务索引,为已中止事务(Aborted Transcation)保存重要的元数据信息。只有启用 Kafka 事务后,这个索引才有可能出现。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220706142040.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220706142040.png) ### AbstractIndex 代码结构 diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/15.RocketMQ\346\212\200\346\234\257\345\206\205\345\271\225\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/15.RocketMQ\346\212\200\346\234\257\345\206\205\345\271\225\347\254\224\350\256\260.md" index c2b13c576a..ce32f4d71c 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/15.RocketMQ\346\212\200\346\234\257\345\206\205\345\271\225\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/21.\345\210\206\345\270\203\345\274\217\351\200\232\344\277\241/15.RocketMQ\346\212\200\346\234\257\345\206\205\345\271\225\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《RocketMQ 技术内幕》笔记 date: 2022-07-12 16:58:58 +order: 15 categories: - 笔记 - 分布式 @@ -41,7 +42,7 @@ permalink: /pages/9708e2/ #### 设计理念 -RocketMQ 设计基于主题的发布与订阅模式, 其核心功能包括:消息发送、消息存储( Broker )、消息消费。其整体设计追求简单与性能第一,主要体现在如下三个方面: +RocketMQ 设计基于主题的订阅与发布模式, 其核心功能包括:消息发送、消息存储( Broker )、消息消费。其整体设计追求简单与性能第一,主要体现在如下三个方面: - 自研 NameServer,而不是用 ZooKeeper 作为注册中心。因为 ZooKeeper 采用 CAP 模型中的 CP 模型,其实并不适用于注册中心的业务模式。 - RocketMQ 的消息存储文件设计成文件组的概念,组内单个文件大小固定,方便引入内存映射机制,所有主 diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.24\350\256\262\345\220\203\351\200\217\345\210\206\345\270\203\345\274\217\346\225\260\346\215\256\345\272\223.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.24\350\256\262\345\220\203\351\200\217\345\210\206\345\270\203\345\274\217\346\225\260\346\215\256\345\272\223.md" new file mode 100644 index 0000000000..ec2699a7ef --- /dev/null +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.24\350\256\262\345\220\203\351\200\217\345\210\206\345\270\203\345\274\217\346\225\260\346\215\256\345\272\223.md" @@ -0,0 +1,193 @@ +--- +title: 《24 讲吃透分布式数据库》笔记 +date: 2023-07-25 22:41:52 +order: 01 +categories: + - 笔记 + - 分布式 + - 分布式存储 +tags: + - 分布式 + - 分布式存储 +permalink: /pages/e08961/ +--- + +# 《24 讲吃透分布式数据库》笔记 + +## 开篇词 吃透分布式数据库,提升职场竞争力 + +略 + +## 导论:什么是分布式数据库?聊聊它的前世今生 + +### 分布式数据库的核心 + +数据分片 + +- 水平分片:按行进行数据分割,数据被切割为一个个数据组,分散到不同节点上。 + +- 垂直分片:按列进行数据切割,一个数据表的模式(Schema)被切割为多个小的模式。 + +数据同步 + +数据库同步用于帮助数据库恢复一致性。 + +**分布式数据库发展就是一个由合到分,再到合的过程**: + +1. 早期的关系型商业数据库的分布式能力可以满足大部分用户的场景,因此产生了如 Oracle 等几种巨无霸数据库产品; +2. OLAP 领域首先寻求突破,演化出了大数据技术与 MPP 类型数据库,提供功能更强的数据分析能力; +3. 去 IOE 引入数据库中间件,并结合应用平台与开源单机数据库形成新一代解决方案,让商业关系型数据库走下神坛,NoSQL 数据库更进一步打破了关系型数据库唯我独尊的江湖地位; +4. 新一代分布式 OLTP 数据库正式完成了分布式领域对数据库核心特性的完整支持,它代表了分布式数据库从此走向了成熟,也表明了 OLAP 与 OLTP 分布式场景下,分别在各自领域内取得了胜利; +5. HTAP 和多模式数据处理的引入,再一次将 OLAP 与 OLTP 融合,从而将分布式数据库推向如传统商业关系型数据库数十年前那般的盛况,而其产生的影响要比后者更为深远。 + +## SQL vs NoSQL:一次搞清楚五花八门的“SQL” + +略 + +## 数据分片:如何存储超大规模的数据? + +### 数据分片概论 + +想提升系统对于数据的处理,有两种思路: + +垂直扩展:提升硬件设备,获得更好的 CPU、更大的内存。但这种方式容易达到瓶颈。 + +水平扩展:采用分而治之的思想,将数据拆分成多个分区,分散到一组便宜的机器上。这种方式性价比更高,不过也会引入数据同步等复杂的问题。 + +### 分片算法 + +哈希分片 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230725224346.png) + +范围分片 + +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230725224415.png) + +### 分布式 ID + +UUID:性能较差,且离散度不高 + +雪花算法 + +## 数据复制:如何保证数据在分布式场景下的高可用? + +### 主从复制 + +- **复制同步模式** + - 同步复制:如果由于从库已崩溃,存在网络故障或其他原因而没有响应,则主库也无法写入该数据。 + - 半同步复制:其中部分从库进行同步复制,而其他从库进行异步复制。也就是,如果其中一个从库同步确认,主库可以写入该数据。 + - 异步复制:不管从库的复制情况如何,主库可以写入该数据。而此时,如果主库失效,那么还未同步到从库的数据就会丢失。 +- 复制延迟 - 提高系统的查询性能,可以通过添加从节点来实现。但是如果使用同步复制,每次写入都需要同步所有从节点,会造成一部分从节点已经有数据,但是主节点还没写入数据。而异步复制的问题是从节点的数据可能不是最新的。 +- **复制与高可用性** + - **从节点故障**。由于每个节点都复制了从主库那里收到的数据更改日志,因此它知道在发生故障之前已处理的最后一个事务,由此可以凭借此信息从主节点或其他从节点那里恢复自己的数据。 + - **主节点故障**。在这种情况下,需要在从节点中选择一个成为新的主节点,此过程称为故障转移,可以手动或自动触发。其典型过程为:第一步根据超时时间确定主节点离线;第二步选择新的主节点,这里注意**新的主节点通常应该与旧的主节点数据最为接近**;第三步是重置系统,让它成为新的主节点。 +- **复制方式** + - 基于语句的复制:主库记录它所执行的每个写请求(一般以 SQL 语句形式保存),每个从库解析并执行该语句,就像从客户端收到该语句一样。但这种复制会有一些潜在问题,如语句使用了获取当前时间的函数,复制后会在不同数据节点上产生不同的值。另外如自增列、触发器、存储过程和函数都可能在复制后产生意想不到的问题。但可以通过预处理规避这些问题。使用该复制方式的分布式数据库有 VoltDB、Calvin。 + - 日志(WAL)同步:WAL 是一组字节序列,其中包含对数据库的所有写操作。它的内容是一组低级操作,如向磁盘的某个页面的某个数据块写入一段二进制数据,主库通过网络将这样的数据发送给从库。这种方法避免了上面提到的语句中部分操作复制后产生的一些副作用,但要求主从的数据库引擎完全一致,最好版本也要一致。如果要升级从库版本,那么就需要计划外停机。PostgreSQL 和 Oracle 中使用了此方法。 + - 行复制:它由一系列记录组成,这些记录描述了以行的粒度对数据库表进行的写操作。它与特定存储引擎解耦,并且第三方应用可以很容易解析其数据格式。 + - ETL 工具:该功能一般是最灵活的方式。用户可以根据自己的业务来设计复制的范围和机制,同时在复制过程中还可以进行如过滤、转换和压缩等操作。但性能一般较低,故适合处理子数据集的场景。 + +## 一致性与 CAP 模型:为什么需要分布式一致性? + +略 + +## 实践:设计一个最简单的分布式数据库 + +略 + +## 概要:什么是存储引擎,为什么需要了解它? + +### 存储引擎 + +数据库的一般架构 + +1. 传输层:它是接受客户端请求的一层。用来处理网络协议。同时,在分布式数据库中,它还承担着节点间互相通信的职责。 +2. 查询层:请求从传输层被发送到查询层。在查询层,协议被进行解析,如 SQL 解析;后进行验证与分析;最后结合访问控制来决定该请求是否要被执行。解析完成后,请求被发送到查询优化器,在这里根据预制的规则,数据分布并结合数据库内部的统计,会生成该请求的执行计划。执行计划一般是树状的,包含一系列相关的操作,用于从数据库中查询到请求希望获取的数据。 +3. 执行层:执行计划被发送到执行层去运行。执行层一般包含本地运行单元与远程运行单元。根据执行计划,调用不同的单元,而后将结果合并返回到传输层。执行层本地运行单元其实就是存储引擎。它一般包含如下一些功能 + 1. 事务管理器:用来调度事务并保证数据库的内部一致性(这与模块一中讨论的分布式一致性是不同的); + 2. 锁管理:保证操作共享对象时候的一致性,包括事务、修改数据库参数都会使用到它; + 3. 存储结构:包含各种物理存储层,描述了数据与索引是如何组织在磁盘上的; + 4. 内存结构:主要包含缓存与缓冲管理,数据一般是批量输入磁盘的,写入之前会使用内存去缓存数据; + 5. 提交日志:当数据库崩溃后,可以使用提交日志恢复系统的一致性状态。 + +### 内存与磁盘 + +内存特点:查询快、更昂贵、持久化比较复杂 + +### 行式存储与列式存储 + +**列式存储非常适合处理分析聚合类型的任务**,如计算数据趋势、平均值,等等。因为这些数据一般需要加载一列的所有行,而不关心的列数据不会被读取,从而获得了更高的性能。 + +### 数据文件与索引文件 + +数据文件最传统的形式为堆组织表(Heap-Organized Table),数据的放置没有一个特别的顺序,一般是按照写入的先后顺序排布。这种数据文件需要一定额外的索引帮助来查找数据。 + +索引文件的分类模式一般为主键索引与二级索引两类。前者是建立在主键上的,它可能是一个字段或多个字段组成。而其他类型的索引都被称为二级索引。主键索引与数据是一对一关系,而二级索引很有可能是一对多的关系,即多个索引条目指向一条数据。 + +### 面向分布式的存储引擎特点 + +**内存型数据库会倾向于选择分布式模式来进行构建**。原因也是显而易见的,由于单机内存容量相比磁盘来说是很小的,故需要构建分布式数据库来满足业务所需要的容量。 + +**列式存储也与分布式数据库存在天然的联系**。原因是针对 OLAP 的分析数据库,一个非常大的应用场景就是要分析所有数据。 + +## 分布式索引:如何在集群中快速定位数据? + +### 读取路径 + +存储引擎处理查询请求一般流程: + +1. 寻找分片和目标节点; +2. 检查数据是否在缓存与缓冲中; +3. 检查数据是否在磁盘文件中; +4. 合并结果。 + +### 索引数据表 + +SSTable 文件是一个排序的、不可变的、持久化的键值对结构,其中键值对可以是任意字节的字符串,支持使用指定键来查找值,或通过给定键范围遍历所有的键值对。每个 SSTable 文件包含一系列的块。SSTable 文件中的块索引(这些块索引通常保存在文件尾部区域)用于定位块,这些块索引在 SSTable 文件被打开时加载到内存。在查找时首先从内存中的索引二分查找找到块,然后一次磁盘寻道即可读取到相应的块。另一种方式是将 SSTable 文件完全加载到内存,从而在查找和扫描中就不需要读取磁盘。 + +### 内存缓冲 + +内存中常用的快速搜索数据结构是跳表。典型代表:Redis 使用跳表实现 zset。 + +### 布隆过滤 + +## 日志型存储:为什么选择它作为底层存储? + +## 事务处理与恢复(上):数据库崩溃后如何保证数据不丢失? + +## 事务处理与恢复(下):如何控制并发事务? + +## 引擎拓展:解读当前流行的分布式存储引擎 + +## 概要:分布式系统都要解决哪些问题? + +## 错误侦测:如何保证分布式系统稳定? + +## 领导选举:如何在分布式系统内安全地协调操作? + +## 再谈一致性:除了 CAP 之外的一致性模型还有哪些? + +## 数据可靠传播:反熵理论如何帮助数据库可靠工作? + +## 分布式事务(上):除了 XA,还有哪些原子提交算法吗? + +## 分布式事务(下):Spanner 与 Calvin + +## 共识算法:一次性说清楚 Paxos、Raft 等算法的区别 + +## 知识串讲:如何取得性能和可扩展性的平衡? + +## 发展与局限:传统数据库在分布式领域的探索 + +## 数据库中间件:传统数据库向分布式数据库的过渡 + +## 现状解读:分布式数据库的最新发展情况 + +## 加餐 1 概念解析:云原生、HTAP、图与内存数据库 + +## 加餐 2 数据库选型:我们该用什么分布式数据库? + +## 参考资料 + +- [《24 讲吃透分布式数据库》](https://kaiwu.lagou.com/course/courseInfo.htm?courseId=605) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.hbase-a-nosql-database.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.hbase-a-nosql-database.md" new file mode 100644 index 0000000000..355cfa9124 --- /dev/null +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/01.hbase-a-nosql-database.md" @@ -0,0 +1,88 @@ +--- +title: 《HBase A NoSQL database》笔记 +date: 2023-09-05 19:52:01 +order: 01 +categories: + - 笔记 + - 分布式 + - 分布式存储 +tags: + - 分布式 + - 分布式存储 + - HBASE +permalink: /pages/b2f10e/ +--- + +# 《HBase: A NoSQL database》笔记 + +## 简介 + +HBase 是一种 NoSQL 数据库,它是Java版本的 Google’s Big Table 实现,它原本是 Hadoop 的子项目,现在已独立出来,并成为 apache 的顶级项目。 + +HBase 的设计目标是用于存储大规模数据集。HBase 是列式数据库,与传统行式数据库相比,其非常适合用于存储稀疏性的数据。 + +HBase 是基于 HDFS 实现的。 + +## HBase 和历史 + +HBase 关键特性: + +- 水平扩展 +- 分区容错性 +- 支持并行处理 +- 支持 HDFS 和 MapReduce +- 近实时查询 +- 适用于存储大规模数据集 +- 适用于存储稀疏型数据(宽表) +- 表的动态负载均衡 +- 对于大规模的查询,支持块缓存和布隆过滤器 + +HBase 发展历史 + +2007 - Mike Cafarella 发布 BigTable 的开源实现——HBase + +2008 ~ 2010 - HBase 成为 Apache 顶级项目。 + +## HBase 数据结构和架构 + +HBase 表可以用于 MapReduce 任务的输入、输出对象。 + +HBase 由行、列族、列、时间戳组成。 + +HBase 表会被分成多个分区,每个分区会定义起始key、结束key。它们被存于 HDFS 文件中。 + +HBase 的架构通常为一个 master server,多个 region server,以及 ZooKeeper 集群。 + +- **master server** + - 在 ZooKeeper 的帮助下,为分区分配 region server,控制 region server 的负载均衡。 + - 负责 schema 的变更 + - 管理和监控 Hadoop 集群 +- **region server** + - region server 负责处理来自客户端的 CRUD 操作 + - region server 包括内存存储和 HFile + - region server 运行在 HDFS 的数据节点上 + - region server 有四个核心组件:Block cache(读缓存)、MemStore(写缓存)、WAL、HFile(存储行数据,键值对结构) +- Zookeeper + - 当 region server 宕机并重新工作时,HBase 会使用 ZooKeeper 作为协调工具,对其进行恢复 + - Zookeeper 是客户端和 master server 的中心,它维护着 master server 和 region server 注册的元数据信息。例如:有多少有效的 region server;任意 region server 持有哪些 data node + - ZooKeeper 可以用于追踪服务器错误 + +## HBase 和大数据 + +HBase 相比于其他 NoSQL,最显著的优势在于,它属于 Hadoop 生态体系中的重要一环,被广泛用于大数据领域。但是,近些年,有 MongoDB、Cassandra 等一些数据库挑战着其地位。 + +## HBase 的应用 + +Facebook 的消息平台使用 HBase 存储数据,每月产生约 13.5 亿条信息。 + +HBase 还被用于存储各种海量操作数据。 + +## HBase 的挑战和限制 + +HBase 采用主从架构,一旦 master server 不可用,需要很长时间才能恢复。 + +HBase 不支持二级索引。 + +## 参考资料 + +- [HBase: A NoSQL Database](https://www.researchgate.net/publication/317399857_HBase_A_NoSQL_Database) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/02.the-log-structured-merge-tree.md" "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/02.the-log-structured-merge-tree.md" new file mode 100644 index 0000000000..9d8178e457 --- /dev/null +++ "b/source/_posts/99.\347\254\224\350\256\260/15.\345\210\206\345\270\203\345\274\217/22.\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250/02.the-log-structured-merge-tree.md" @@ -0,0 +1,29 @@ +--- +title: 《The Log-Structured Merge-Tree (LSM-Tree)》笔记 +date: 2023-09-05 19:52:01 +order: 02 +categories: + - 笔记 + - 分布式 + - 分布式存储 +tags: + - 分布式 + - 分布式存储 + - HBASE +permalink: /pages/d780e2/ +--- + +# 《The Log-Structured Merge-Tree (LSM-Tree)》笔记 + +LSM 被广泛应用于很多以文件结构存储数据的数据库,如:HBase, Cassandra, LevelDB, SQLite。 + +LSM 的设计目标:通过顺序写来提高写操作吞吐量,替代传统的 B+ 树或 ISAM。 + +## 参考资料 + +- 原文 +- [The Log-Structured-Merge-Tree](chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/https://www.cs.umb. + edu/~poneil/lsmtree.pdf) +- 扩展阅读 +- [Log Structured Merge Trees(LSM) 原理](https://www.open-open.com/lib/view/open1424916275249.html) +- [Log Structured Merge Tree](chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/https://lrita.github.io/images/posts/database/lsmtree-170129180333.pdf) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/01.\344\273\2160\345\274\200\345\247\213\345\255\246\345\244\247\346\225\260\346\215\256\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/01.\344\273\2160\345\274\200\345\247\213\345\255\246\345\244\247\346\225\260\346\215\256\347\254\224\350\256\260.md" index 253008ddcd..48540725cf 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/01.\344\273\2160\345\274\200\345\247\213\345\255\246\345\244\247\346\225\260\346\215\256\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/01.\344\273\2160\345\274\200\345\247\213\345\255\246\345\244\247\346\225\260\346\215\256\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《从 0 开始学大数据》笔记 date: 2023-03-13 17:01:51 +order: 01 categories: - 笔记 - 大数据 @@ -31,7 +32,7 @@ Doug Cutting 根据 Google 论文开发了 Hadoop。 大数据要存入分布式文件系统(HDFS),要有序调度 MapReduce 和 Spark 作业执行,并能把执行结果写入到各个应用系统的数据库中,还需要有一个大数据平台整合所有这些大数据组件和企业应用系统。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230223134708.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230223134708.png) ### 02 丨预习 02 丨大数据应用发展史:从搜索引擎到人工智能 @@ -91,7 +92,7 @@ Doug Cutting 根据 Google 论文开发了 Hadoop。 - 数据读写速度的问题。RAID 根据可以使用的磁盘数量,将待写入的数据分成多片,并发同时向多块磁盘进行写入,显然写入的速度可以得到明显提高;同理,读取速度也可以得到明显提高。不过,需要注意的是,由于传统机械磁盘的访问延迟主要来自于寻址时间,数据真正进行读写的时间可能只占据整个数据访问时间的一小部分,所以数据分片后对 N 块磁盘进行并发读写操作并不能将访问速度提高 N 倍。 - 数据可靠性的问题。使用 RAID 10、RAID 5 或者 RAID 6 方案的时候,由于数据有冗余存储,或者存储校验信息,所以当某块磁盘损坏的时候,可以通过其他磁盘上的数据和校验数据将丢失磁盘上的数据还原。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230224131454.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230224131454.png) 实现更强的计算能力和更大规模的数据存储有两种思路 @@ -102,7 +103,7 @@ Doug Cutting 根据 Google 论文开发了 Hadoop。 ### 06 | 新技术层出不穷,HDFS 依然是存储的王者 -![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/bigdata/hdfs/hdfs-architecture.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/cs/bigdata/hdfs/hdfs-architecture.png) HDFS 有两个关键组件:DataNode 和 NameNode: @@ -111,7 +112,7 @@ HDFS 有两个关键组件:DataNode 和 NameNode: 为保证高可用,HDFS 会,会将一个数据块复制为多份(默认为 3 份),并将多份相同的数据块存储在不同的服务器上,甚至不同的机架上。 -![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200224203958.png) +![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20200224203958.png) HDFS 故障容错: @@ -154,7 +155,7 @@ TaskTracker 进程。这个进程负责启动和管理 Map 进程以及 Reduce 服务器集群资源调度管理和 MapReduce 执行过程耦合在一起,如果想在当前集群中运行其他计算任务,比如 Spark 或者 Storm,就无法统一使用集群中的资源了。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230227195344.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230227195344.png) Yarn 包括两个部分: @@ -226,7 +227,7 @@ Spark 有三个主要特性:RDD 的编程模型更简单,DAG 切分的多阶 #### HBase 可伸缩架构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230303145832.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230303145832.png) HBase 的伸缩性主要依赖其可分裂的 HRegion 及可伸缩的分布式文件系统 HDFS 实现。 @@ -238,7 +239,7 @@ HRegionServer 是物理服务器,每个 HRegionServer 上可以启动多个 HR 应用程序通过 ZooKeeper 获得主 HMaster 的地址,输入 Key 值获得这个 Key 所在的 HRegionServer 地址,然后请求 HRegionServer 上的 HRegion,获得所需要的数据。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230303150211.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230303150211.png) HRegion 会把数据存储在若干个 HFile 格式的文件中,这些文件使用 HDFS 分布式文件系统存储,在整个集群内分布并高可用。当一个 HRegion 中数据量太多时,这个 HRegion 连同 HFile 会分裂成两个 HRegion,并根据集群中服务器负载进行迁移。如果集群中有新加入的服务器,也就是说有了新的 HRegionServer,由于其负载较低,也会把 HRegion 迁移过去并记录到 HMaster,从而实现 HBase 的线性伸缩。 @@ -252,7 +253,7 @@ HBase 这种列族的数据结构设计,实际上是把字段的名称和字 HBase 使用了一种叫作 LSM 树(Log 结构合并树)的数据结构进行数据存储。数据写入的时候以 Log 方式连续写入,然后异步对磁盘上的多个 LSM 树进行合并。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230303154832.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230303154832.png) LSM 树可以看作是一个 N 阶合并树。数据写操作(包括插入、修改、删除)都在内存中进行,并且都会创建一个新记录(修改会记录新的数据值,而删除会记录一个删除标志)。这些数据在内存中仍然还是一棵排序树,当数据量超过设定的内存阈值后,会将这棵排序树和磁盘上最新的排序树合并。当这棵排序树的数据量也超过设定阈值后,会和磁盘上下一级的排序树合并。合并过程中,会用最新更新的数据覆盖旧的数据(或者记录为不同版本)。 @@ -317,13 +318,13 @@ Spark 性能优化可以分解为下面几步。 ### 26 | 互联网产品 + 大数据产品 = 大数据平台 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313105947.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313105947.png) - 数据采集:数据库同步通常用 Sqoop,日志同步可以选择 Flume,打点采集的数据经过格式化转换后通过 Kafka 等消息队列进行传递 - 数据处理:离线计算:MapReduce、Hive、Spark;实时计算:Storm、Spark Streaming、Flink - 数据展示:Lambda 架构 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313111021.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313111021.png) ### 27 | 大数据从哪里来? @@ -343,15 +344,15 @@ Spark 性能优化可以分解为下面几步。 淘宝大数据平台 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313113641.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313113641.png) 美团大数据平台 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313113700.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313113700.png) 滴滴大数据平台 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313113720.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313113720.png) ### 29 | 盘点可供中小企业参考的商业大数据平台 @@ -359,7 +360,7 @@ Spark 性能优化可以分解为下面几步。 CDH、TDH -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313114058.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313114058.png) CDH 是一个大数据集成平台,将主流大数据产品都集成到这个平台中,企业可以使用 CDH 一站式部署整个大数据技术栈。从架构分层角度,CDH 可以分为 4 层:系统集成,大数据存储,统一服务,过程、分析与计算。 @@ -380,7 +381,7 @@ CDH 是一个大数据集成平台,将主流大数据产品都集成到这个 ### 30 | 当大数据遇上物联网 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313144317.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313144317.png) 1. 智能网关通过消息队列将数据上传到物联网大数据平台,Storm 等流式计算引擎从消息队列获取数据,对数据的处理分三个方面。数据进行清理转换后写入到大数据存储系统。调用规则和机器学习模型,对上传数据进行计算,如果触发了某种执行规则,就将控制信息通过设备管理服务器下发给智能网关,并进一步控制终端智能设备。 2. Spark 等离线计算引擎定时对写入存储系统的数据进行批量计算处理,进行全量统计分析和机器学习,并更新机器学习模型。 @@ -407,13 +408,13 @@ CDH 是一个大数据集成平台,将主流大数据产品都集成到这个 #### A/B 测试的过程 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313151335.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313151335.png) #### A/B 测试的系统架构 A/B 测试系统最重要的是能够根据用户 ID(或者设备 ID)将实验配置参数分发给应用程序,应用程序根据配置参数决定给用户展示的界面和执行的业务逻辑 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313151508.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313151508.png) #### 灰度发布 @@ -488,7 +489,7 @@ K-means 算法 ### 40 丨机器学习的数学原理是什么? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230313161656.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20230313161656.png) 样本:样本就是通常我们常说的“训练数据”,包括输入和结果两部分。 @@ -504,4 +505,4 @@ K-means 算法 ## 参考资料 -- [从 0 开始学大数据](https://time.geekbang.org/column/intro/100020201) +- [从 0 开始学大数据](https://time.geekbang.org/column/intro/100020201) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/02.\345\244\247\350\247\204\346\250\241\346\225\260\346\215\256\345\244\204\347\220\206\345\256\236\346\210\230\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/02.\345\244\247\350\247\204\346\250\241\346\225\260\346\215\256\345\244\204\347\220\206\345\256\236\346\210\230\347\254\224\350\256\260.md" index 7f1b88d8bd..2579281155 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/02.\345\244\247\350\247\204\346\250\241\346\225\260\346\215\256\345\244\204\347\220\206\345\256\236\346\210\230\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/16.\345\244\247\346\225\260\346\215\256/02.\345\244\247\350\247\204\346\250\241\346\225\260\346\215\256\345\244\204\347\220\206\345\256\236\346\210\230\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《大规模数据处理实战》笔记 date: 2023-03-15 15:15:07 +order: 02 categories: - 笔记 - 大数据 @@ -135,4 +136,4 @@ Spark Streaming 用时间片拆分了无限的数据流,然后对每一个数 ## 参考资料 -- [大规模数据处理实战](https://time.geekbang.org/column/intro/100025301) +- [大规模数据处理实战](https://time.geekbang.org/column/intro/100025301) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/17.\344\272\272\345\267\245\346\231\272\350\203\275/01.\346\234\272\345\231\250\345\255\246\344\271\24040\350\256\262\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/17.\344\272\272\345\267\245\346\231\272\350\203\275/01.\346\234\272\345\231\250\345\255\246\344\271\24040\350\256\262\347\254\224\350\256\260.md" index ee9f0877dc..7693ed7002 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/17.\344\272\272\345\267\245\346\231\272\350\203\275/01.\346\234\272\345\231\250\345\255\246\344\271\24040\350\256\262\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/17.\344\272\272\345\267\245\346\231\272\350\203\275/01.\346\234\272\345\231\250\345\255\246\344\271\24040\350\256\262\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《机器学习 40 讲》笔记 date: 2023-02-09 21:08:48 +order: 01 categories: - 笔记 - 人工智能 @@ -66,4 +67,4 @@ permalink: /pages/c3ab9e/ 如果训练数据中的每组输入都有其对应的输出结果,这类学习任务就是**监督学习(supervised learning)**,对没有输出的数据进行学习则是**无监督学习(unsupervised learning)**。监督学习具有更好的预测精度,无监督学习则可以发现数据中隐含的结构特性,起到的也是分类的作用,只不过没有给每个类别赋予标签而已。无监督学习可以用于对数据进行聚类或者密度估计,也可以完成异常检测这类监督学习中的预处理操作。直观地看,监督学习适用于预测任务,无监督学习适用于描述任务。 -## 04 丨计算学习理论 +## 04 丨计算学习理论 \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\344\271\213\347\276\216\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\344\271\213\347\276\216\347\254\224\350\256\260.md" index 191bd67d3c..ed47ebc45d 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\344\271\213\347\276\216\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/21.\350\275\257\344\273\266\345\267\245\347\250\213/01.\350\275\257\344\273\266\345\267\245\347\250\213\344\271\213\347\276\216\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《软件工程之美》笔记 date: 2022-07-12 13:20:31 +order: 01 categories: - 笔记 - 软件工程 @@ -27,7 +28,7 @@ permalink: /pages/06f95a/ **有目的、有计划、有步骤地解决问题的方法就是工程方法。** -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712132650.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712132650.png) 工程方法通常会分成六个阶段:想法、概念、计划、设计、开发和发布。 @@ -40,7 +41,7 @@ permalink: /pages/06f95a/ ## 瀑布模型:像工厂流水线一样把软件开发分层化 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712133102.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712133102.png) 瀑布模型把整个项目过程分成了六个主要阶段: @@ -51,7 +52,7 @@ permalink: /pages/06f95a/ - **软件测试**:在编码完成后,对可运行的结果对照需求分析文档进行严密的测试。如果测试发现问题,需要修复。最终测试完成后,形成测试报告。 - **运行维护**:在软件开发完成,正式运行投入使用。后续需要继续维护,修复错误和增加功能。交付时需要提供使用说明文档。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712133357.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712133357.png) ## 瀑布模型之外,还有哪些开发模型? @@ -69,7 +70,7 @@ permalink: /pages/06f95a/ 增量模型是把待开发的软件系统模块化,然后在每个小模块的开发过程中,应用一个小瀑布模型,对这个模块进行需求分析、设计、编码和测试。相对瀑布模型而言,增量模型周期更短,不需要一次性把整个软件产品交付给客户,而是分批次交付。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712134154.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712134154.png) 因为增量模型的根基是模块化,所以,**如果系统不能模块化,那么将很难采用增量模型的模式来开发。**另外,对模块的划分很抽象,这本身对于系统架构的水平是要求很高的。 @@ -83,7 +84,7 @@ permalink: /pages/06f95a/ 在一个迭代中都会包括需求分析、设计、实现和测试,类似于一个小瀑布模型。**迭代结束时要完成一个可以运行的交付版本。** -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712134329.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712134329.png) **增量模型是按照功能模块来拆分;而迭代模型则是按照时间来拆分,看单位时间内能完成多少功能。** @@ -93,7 +94,7 @@ V 模型适合外包项目。V 模型本质上还是瀑布模型,只不过它 针对从需求定义一直到编码阶段,每个阶段都有对应的测试验收。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712134518.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712134518.png) ### 螺旋模型 @@ -101,7 +102,7 @@ V 模型适合外包项目。V 模型本质上还是瀑布模型,只不过它 这种情况,基于增量模型或者迭代模型进行开发,就可以有效降低风险。你需要注意的是,在每次交付的时候,要同时做一个风险评估,如果风险过大就不继续后续开发了,及时止损。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712134638.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712134638.png) 这种强调风险,以风险驱动的方式完善项目的开发模型就是螺旋模型。 @@ -115,7 +116,7 @@ V 模型适合外包项目。V 模型本质上还是瀑布模型,只不过它 ### 一切工作任务围绕 Ticket 开展 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220712135814.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220712135814.png) - 每一个任务的状态都可以被跟踪起来:什么时候开始做的,谁在做,做完没有。 - 整个团队在做什么一目了然。 diff --git "a/source/_posts/99.\347\254\224\350\256\260/96.\345\267\245\344\275\234/01.\350\201\214\345\234\272\346\261\202\347\224\237\346\224\273\347\225\245\347\254\224\350\256\260.md" "b/source/_posts/99.\347\254\224\350\256\260/96.\345\267\245\344\275\234/01.\350\201\214\345\234\272\346\261\202\347\224\237\346\224\273\347\225\245\347\254\224\350\256\260.md" index 5b5a149828..e43c03c4ba 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/96.\345\267\245\344\275\234/01.\350\201\214\345\234\272\346\261\202\347\224\237\346\224\273\347\225\245\347\254\224\350\256\260.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/96.\345\267\245\344\275\234/01.\350\201\214\345\234\272\346\261\202\347\224\237\346\224\273\347\225\245\347\254\224\350\256\260.md" @@ -1,6 +1,7 @@ --- title: 《职场求生攻略》笔记 date: 2022-07-11 07:23:21 +order: 01 categories: - 笔记 - 工作 @@ -16,7 +17,7 @@ permalink: /pages/420981/ 以利益为视角,以换位思考为手段。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220711064451.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220711064451.png) ## 优先级:工作中那么多事情,我要如何安排优先级? @@ -38,7 +39,7 @@ permalink: /pages/420981/ 我们做事情的时候,如果能把其中的每一步都想清楚,理清依赖关系,安排得井井有条,这就已经事半功倍了。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220711065205.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220711065205.png) ## 沟通:邮件那么重要,你还在轻视邮件吗? @@ -64,16 +65,16 @@ permalink: /pages/420981/ 我们都是普通人,普通人没有“背锅”的压力,就没有持久的把事情做好的动力。 -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220711065742.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220711065742.png) ## 沟通:程序员为什么应该爱上交流? -### ![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220711070152.png) +### ![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220711070152.png) ## 主观能动性:为什么程序员,需要发挥主观能动性? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220711070531.png) +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220711070531.png) ## 责任的边界:程序员的职责范围仅仅只是被安排的任务吗? -![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20220711070722.png) \ No newline at end of file +![](https://raw.githubusercontent.com/dunwu/images/master/snap/20220711070722.png) \ No newline at end of file diff --git "a/source/_posts/99.\347\254\224\350\256\260/README.md" "b/source/_posts/99.\347\254\224\350\256\260/README.md" index ce2bb452f2..4bf7f4593e 100644 --- "a/source/_posts/99.\347\254\224\350\256\260/README.md" +++ "b/source/_posts/99.\347\254\224\350\256\260/README.md" @@ -7,12 +7,17 @@ tags: - 笔记 permalink: /pages/aa2c27/ hidden: true +index: false --- # 笔记 ## 📖 内容 +### Java + +- [《玩转 Spring 全家桶》笔记](01.Java/01.玩转Spring全家桶笔记.md) + ### 设计 - [《从 0 开始学架构》笔记](03.设计/01.从0开始学架构笔记.md) @@ -37,7 +42,8 @@ hidden: true - **分布式理论** - [《分布式协议与算法实战》笔记](15.分布式/01.分布式理论/01.分布式协议与算法实战笔记.md) - **分布式通信** - - [《RPC 实战与核心原理》](15.分布式/21.分布式通信/01.RPC实战与核心原理.md) + - [《RPC 实战与核心原理》](15.分布式/21.分布式通信/01.RPC实战与核心原理笔记.md) + - [《Dubbo 源码解读与实战笔记》](15.分布式/21.分布式通信/02.Dubbo源码解读与实战笔记.md) - [《消息队列高手课学习》笔记](15.分布式/21.分布式通信/11.消息队列高手课笔记.md) - [《Kafka 核心源码解读》笔记](15.分布式/21.分布式通信/13.Kafka核心源码解读笔记.md) - [《RocketMQ 技术内幕》笔记](15.分布式/21.分布式通信/15.RocketMQ技术内幕笔记.md) @@ -61,4 +67,4 @@ hidden: true ## 🚪 传送 -◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾ +◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ \ No newline at end of file